Hibernate Join and Restrictions on multiple tables - java

I have three tables to query with Hibernate or JPA...
Tables structure
CREATE TABLE `product` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`insert_date` datetime NOT NULL,
`last_update` datetime NOT NULL,
...
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8;
CREATE TABLE `language` (
`language_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
...
PRIMARY KEY (`language_id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `product_description` (
`product_id` int(11) unsigned NOT NULL,
`language_id` int(11) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`tag` text NOT NULL,
`meta_title` varchar(255) NOT NULL,
`meta_description` varchar(255) NOT NULL,
`meta_keyword` varchar(255) NOT NULL,
PRIMARY KEY (`product_id`,`language_id`),
KEY `name` (`name`),
KEY `product_language_idx` (`language_id`),
CONSTRAINT `product_description` FOREIGN KEY (`product_id`) REFERENCES `product` (`product_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `product_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Product <OneToMany> ProductDescription
Language <ManyToOne> ProductDescription
Assuming my entities are the following:
Product Entity:
#Entity
#Table(name="product")
#NamedQuery(name="Product.findAll", query="SELECT p FROM Product p")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="product_id", unique=true, nullable=false)
private int productId;
....
//bi-directional many-to-one association to ProductDescription
#OneToMany(fetch = FetchType.LAZY, mappedBy = "product")
private List<ProductDescription> productDescriptions;
Language Entity:
#Entity
#Table(name="language")
#NamedQuery(name="Language.findAll", query="SELECT l FROM Language l")
public class Language implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="language_id", unique=true, nullable=false)
private int languageId;
...
//bi-directional many-to-one association to ProductDescription
#ManyToOne(fetch=FetchType.LAZY)
//#JoinColumn(name="language_id", referencedColumnName="id",nullable=false, insertable=false, updatable=false)
private ProductDescription productDescription;
ProductDescription Entity (id is a composite key in class ProductDescriptionPK):
#Entity
#Table(name="product_description")
#NamedQuery(name="ProductDescription.findAll", query="SELECT p FROM ProductDescription p")
public class ProductDescription implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ProductDescriptionPK id;
//bi-directional many-to-one association to Language
#OneToMany(mappedBy="productDescription")
private List<Language> languages;
//bi-directional many-to-one association to Product
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="product_id", nullable=false, insertable=false, updatable=false)
private Product product;
So I have to execute a query as sample, using a join between product and product_description, where language is 1:
SELECT p.product_id, pd.description
FROM product p
INNER JOIN product_description pd
USING ( product_id )
INNER JOIN language
USING ( language_id )
WHERE language_id = 1
Firstly I have used Hibernate with Criteria Query:
Criteria criteria = getSession().createCriteria(Product.class,"p");
criteria.createAlias("p.productDescriptions", "pd");
criteria.createAlias("pd.languages", "l");
criteria.add(Restrictions.eq("l.languageId", new Integer(1)));
result = criteria.list();
But that code retrieves this error:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'l2_.productDescription_language_id' in 'field list'
What's my issue? How can I perform queries like this?
Thanks a lot!

I do not understand completely your data model.
I think that the relationship between a Language and a ProductDescription should be one-to-many from the Language point of view but putting that aside ...
UPDATED:
Effectively, Hibernate do not map correctly the relationships using the annotations that you have indicated above. It's trying to map the strange ManyToOne relation in the table language and it can not find those fields: productDescription_language_id and productDescription_product_id.
I think that the correct mapping for your tables is:
LANGUAGE ENTITY
#Entity
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="language_id", unique=true, nullable=false)
private Long languageId;
private String name;
#OneToMany(fetch=FetchType.LAZY, mappedBy="language")
private List<ProductDescription> productDescriptions =
new ArrayList<ProductDescription>();
// Other fields + getters and setters
}
PRODUCT DESCRIPTION ENTITY
#Entity
#Table(name="product_description")
public class ProductDescription {
#Embeddable
public static class ProductDescriptionPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "product_id")
protected Long productId;
#Column(name = "language_id")
protected Long languageId;
public ProductDescriptionPK() {
}
public ProductDescriptionPK(Long productId, Long languageId) {
super();
this.productId = productId;
this.languageId = languageId;
}
}
#EmbeddedId
private ProductDescriptionPK id;
private String description;
#ManyToOne
#JoinColumn(name="language_id", nullable=false, insertable=false, updatable=false)
private Language language;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="product_id", nullable=false, insertable=false, updatable=false)
private Product product;
// Other fields + getters and setters
}
Here is a working example of how you can chain joins with JPQL using the entities defined as you have declared them on your question.
EntityManager em = emf.createEntityManager();
// [UPDATED] QUERY
String jpql = "SELECT p.id, pd.description FROM Product p "
+ "JOIN p.productDescriptions pd "
+ "JOIN pd.language l WHERE l.language_id = :idLanguage)";
Query query = newEm.createQuery(jpql);
query.setParameter("idLanguage", new Long(1));
List<Object> resultList = query.getResultList();
System.out.println( resultList.size() + " product(s) found:" );
for (Object singleResult : resultList) {
Object[] singleRow = (Object[]) singleResult;
System.out.println(singleRow[0] + " " + singleRow[1]);
}
That code generates this SQL query [UPDATED]
select
product0_.product_id as col_0_0_,
productdes1_.description as col_1_0_
from
Product product0_
inner join
product_description productdes1_
on product0_.product_id=productdes1_.product_id
inner join
Language language2_
on productdes1_.language_id=language2_.language_id
where
language2_.language_id=?
I have been reading some articles and books on the subject and using a left join fetch with a where clause is invalid. Quoting "Java Persistence with Hibernate" by Gavin King et al:
"The query left join fetch i.bids b where b.amount ... is invalid. You can't say, "Load the Item instances and initializes their bids collections, but only with Bid instances that have a certain amount"
Hope this helps.

Related

JPA mappedBy reference an unknown target entity

I am writing a simple inventory database that contains tables for products, orders and customers. The database definition can be found here:
CREATE TABLE public.customers
(
id integer NOT NULL DEFAULT nextval('customers_id_seq'::regclass),
title character varying(10) COLLATE pg_catalog."default" NOT NULL,
first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
middle_names character varying(50) COLLATE pg_catalog."default",
last_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
email character varying(50) COLLATE pg_catalog."default" NOT NULL,
phone_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT customers_pkey PRIMARY KEY (id)
)
CREATE TABLE public.products
(
id integer NOT NULL DEFAULT nextval('products_id_seq'::regclass),
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
sku integer NOT NULL,
inventory_on_hand integer NOT NULL,
reorder_threshold integer NOT NULL,
price numeric(5,2),
inventory_to_be_shipped integer NOT NULL,
CONSTRAINT products_pkey PRIMARY KEY (id)
)
CREATE TABLE public.order_items
(
id integer NOT NULL DEFAULT nextval('order_items_id_seq'::regclass),
product_id integer NOT NULL,
order_id integer NOT NULL,
CONSTRAINT order_items_pkey PRIMARY KEY (id),
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id)
REFERENCES public.orders (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id)
REFERENCES public.products (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE public.orders
(
id integer NOT NULL DEFAULT nextval('orders_id_seq'::regclass),
customer_id integer,
order_date date NOT NULL DEFAULT now(),
arrival_date date,
CONSTRAINT orders_pkey PRIMARY KEY (id),
CONSTRAINT orders_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES public.customers (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
I am trying to implement a Spring Security Resource server to perform CRUD operations on the database. I have implemented entity classes for each table in the database but when try to start the server I get a
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: edu.finalyearproject.imsresourceserver.models.Order.customers in edu.finalyearproject.imsresourceserver.models.Customer.orders
My entity and repository classes can be found below:
Product.java:
#Entity
#Table(name = "products")
#Data
public class Product
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer sku;
private Float price;
private Integer inventory_on_hand;
private Integer reorder_threshold;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
Customer.java
#Entity
#Table(name = "customers")
#Data
public class Customer
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String title;
private String first_name;
private String middle_names;
private String last_name;
private String email;
private String phone_number;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
Order.java
#Entity
#Table(name = "orders")
#Data
public class Order
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;
private Date order_date;
private Date arrival_date;
#ManyToMany(mappedBy = "orders", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> products = new HashSet<>();
}
I know the problem is related to the relationships between the entities, but I haven't been able to find a solution. Any help would be greatly appreciated.
Try to correct this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "orders", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
to this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
See additional explanation in the documentation.
And you should correct also your Product-Order #ManyToMany association. Only one side of this association should use #JoinTable other side should use mappedBy property of the #ManyToMany annotation. Something like this:
#Entity
public class Product
{
// ...
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY
)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
#Entity
public class Order
{
// ...
#ManyToMany(
mappedBy = "orders",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
}
As it is stated in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
Also as this is explained in this section of the documentation:
If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those which, in turn, can lead to N+1 query issues.
For this reason, you should prefer LAZY associations.

Hibernate - Crash with 2 #OneToMany

Hibernate crashes when using more than one #OneToMany entries in my Users.entity and I don't understand why.
I have a table for users (primary key userID) and various other tables which refer to the primary key userID by a foreign key set in database (InnoDB set and foreign key is set in each depending table).
Here an example with three tables:
Table users:
CREATE TABLE `users` (`userID` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `users` ADD PRIMARY KEY (`userID`);
Table: vacationrequests
CREATE TABLE `vacationrequests` (`requestID` int(11) NOT NULL,`userID` int(4) NOT NULL,) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `vacationrequests` ADD CONSTRAINT `userID` FOREIGN KEY (`userID`) REFERENCES `users` (`userID`) ON UPDATE CASCADE; COMMIT;
Table monthend:
CREATE TABLE `monthend` (`monthendID` int(11) NOT NULL,`userID` int(4) NOT NULL,) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Entity Users:
#Entity
#Table(name="users")
public class Users {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="userID")
private int userID;
... .... (other variables)
#OneToMany(fetch = FetchType.LAZY, mappedBy="monthend")
private Set<Monthend> monthend;
#OneToMany(fetch = FetchType.LAZY, mappedBy="vacationrequests")
private Set<Vacationrequests> vacationrequests;
public Users() {
}
Entity Vacationrequests:
#Entity
#Table(name="vacationrequests")
public class Vacationrequests {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="requestID")
private int requestID;
... .... (other variables)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userID", nullable = false)
private Users users;
public Vacationrequests() {
}
Entity Monthend:
#Entity
#Table(name="monthend")
public class Monthend {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="monthendID")
private int monthendID;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userID", nullable = false)
private Users users;
#Column(name="clientID")
private int clientID;
#Column(name="month")
private int month;
#Column(name="year")
private int year;
#Column(name="monthend")
private int monthend;
public Monthend() {
}
Working Query:
List<Object> listJoinUsersMonthend = session.createQuery("from Monthend m inner join m.users as users where m.clientID = :clientID AND m.year = :year AND users.loginclass = 0 AND users.isactive = 1 AND m.monthend = 0 AND m.month < :month order by users.userID asc")
.setInteger("clientID", user.getClientID())
.setInteger("year", year)
.setInteger("month", month)
.getResultList();
This second query i would like to integrate:
List<Object> listJoinVacationrequestsUsers = session.createQuery("from Vacationrequests v inner join v.users as users where v.clientID = :clientID AND v.approved=0 AND v.vacationtype=1 AND YEAR(v.startdate) = :year" )
.setInteger("clientID", user.getClientID())
.setInteger("year", year)
.getResultList();
Everything works fine with just one query and one entry in the Users.entity. As soon as I add the two entries hibernate just crashes and there is no error message. Is it not possible to do two #OneToMany statements in a entity ?
It seems that the problem is in the mapping of the associations in the Users entity.
You should change the mappedBy attribute of the associations to specify the field name that references the current entity in the associated one (in this case users), like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "users")
private Set<Monthend> monthend;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "users")
private Set<Vacationrequests> vacationrequests;

Mapping a weak entity with JPA

My database contains a companies and employees. I have modeled Employee as a weak entity of Company.
My JPA annotations look like:
#Entity
public class Company extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<Employee> employees;
}
Employee.java:
#Entity
public class Employee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne(optional = false)
#JoinColumn(name="company_id", insertable=false, updatable=false)
private Company company;
}
The following SQL code is created:
create table employee (
id bigint auto_increment not null,
company_id bigint not null,
constraint pk_employee primary key (id)
);
alter table employee add constraint fk_employee_company_id foreign key (company_id) references company (id) on delete restrict on update restrict;
What I want is (constraint pk_employee primary key (id, company_id):
create table employee (
id bigint auto_increment not null,
company_id bigint not null,
constraint pk_employee primary key (id, company_id)
);
alter table employee add constraint fk_employee_company_id foreign key (company_id) references company (id) on delete restrict on update restrict;
Is there a way to create such SQL script?
EDIT:
Letting Employee implement Serializable does not do the trick.
Caused by: javax.persistence.PersistenceException: Could not find BeanDescriptor for class models.Company. Perhaps the EmbeddedId class is not registered?
at io.ebeaninternal.server.deploy.BeanEmbeddedMetaFactory.create(BeanEmbeddedMetaFactory.java:26)
at io.ebeaninternal.server.deploy.BeanPropertyAssocOne.<init>(BeanPropertyAssocOne.java:79)
at io.ebeaninternal.server.deploy.BeanPropertyAssocOne.<init>(BeanPropertyAssocOne.java:62)
at io.ebeaninternal.server.deploy.meta.DeployBeanTable.createProperty(DeployBeanTable.java:68)
at io.ebeaninternal.server.deploy.meta.DeployBeanTable.createIdProperties(DeployBeanTable.java:59)
at io.ebeaninternal.server.deploy.BeanTable.<init>(BeanTable.java:42)
Thanks to #Alan Hay for giving us the link
https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Composite_Primary_Keys
Here is how you create a weak entity Address that takes it's Id (user_id) from the table pmo_user
pmo_user{id, ....}
test_address{user_id, address}
============================
#Entity
#Table(name="test_address")
public class Address
{
#Id
#Column(name="user_id")
protected int userId;
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="id")
protected PmoUser owner;
#Column(name="address")
protected String address;
public void setOwner(PmoUser owner)
{
this.owner = owner;
this.userId = owner.getId();
}
#Override
public String toString() {
return "Address [userId=" + userId + ", owner=" + owner + ", address=" + address + "]";
}
}
Just add #Id annotation under your #JoinColumn annotation. But Employee class must implements Serializable to use this solution.

Spring JPA Repository query filter by a relationship table

If I have a many-to-many relationship between JPA entities as below, how can I retrieve a list of Person (I am interested in the person attributes) that are employees of a specific company?
The relationship between Person and Company is many-to-many. The relationship table Employee has the FK to Person and Company, and a start_date and end_date to indicate when the employment started and finished.
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
}
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
}
#Entity
public class CompanyEmployee {
//note this is to model a relationship table. Am I doing this wrong?
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "start_date", nullable = false)
private LocalDate startDate;
#Column(name = "end_date", nullable = false)
private LocalDate endDate;
#ManyToOne
private Company company;
#ManyToOne
private Person person;
}
Do I use a #Query on the CompanyEmployeeJPARepository? How should I tackle it?
public interface CompanyEmployeeRepository extends JpaRepository<CompanyEmployee,Long> {
//
}
Pablo,
Our company is in the process of converting our existing Spring / MyBatis code to Spring Data JPA, so I have been learning Spring Data JPA for a few weeks. I'm clearly not an expert, but I worked out an example similar to yours which may help you.
I have Person and Company classes that are similar to yours, but (as Jens mentioned), you need lists with OneToMany annotations. I used a separate join table (named company_person) which only has companyId, personId columns to maintain the many-to-many relationship. See the code below.
I did not see a way to put the start/end dates in the company_person join table, so I made a separate (4th table) for that. I called it employment_record with Java class entity EmploymentRecord. It has the combo primary key (companyId, personId) and the start/end dates.
You need repositories for Person, Company, and EmploymentRecord. I extended CrudRepository instead of JpaRepository. But, you don't need an entity or repository for the join table (company_record).
I made a Spring Boot Application class to test it out. I used CascadeType.ALL on Person's OneToMany. In my Application test, I tested that I can change the companies assigned to a person and Spring Data propagates all the changes needed to the Company entities and join table.
However, I had to manually update the EmploymentRecord entities, via its repository. For example, I had to add a start_date each time I added a company to a person. Then, add an end_date when I removed that company from that person. There is probably some way to automate this. The Spring / JPA audit feature is a possibility, so check that out.
The answer to your question:
how can I retrieve a list of Person (I am interested in the person
attributes) that are employees of a specific company?
You simply use companyRepository's findOne(Long id) method followed by getPersonList() method.
snippet from Application.java:
PersonRepository pRep = context.getBean(PersonRepository.class);
CompanyRepository cRep = context.getBean(CompanyRepository.class);
EmploymentRecordRepository emplRep = context.getBean(EmploymentRecordRepository.class);
...
// fetch a Company by Id and get its list of employees
Company comp = cRep.findOne(5L);
System.out.println("Found a company using findOne(5L), company= " + comp.getName());
System.out.println("People who work at " + comp.getName());
for (Person p : comp.getPersonList()) {
System.out.println(p);
}
Here are some references that I found to be useful:
Spring Data JPA tutorial
Join Table example
Person.java:
#Entity
public class Person {
// no-arg constructor
Person() { }
// normal use constructor
public Person(String name, String address) {
this.name = name;
this.address = address;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Version
private int versionId;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name="company_person",
joinColumns={#JoinColumn(name="person_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="company_id", referencedColumnName="id")})
private List<Company> companyList;
// Getters / setters
}
Company.java:
#Entity
public class Company {
// no-arg constructor
Company() { }
// normal use constructor
public Company(String name, String address) {
this.name = name;
this.address = address;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Version
private int versionId;
//#OneToMany(cascade=CascadeType.ALL)
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name="company_person",
joinColumns={#JoinColumn(name="company_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="person_id", referencedColumnName="id")})
private List<Person> personList;
// Getters / Setters
}
EmploymentRecord.java:
#Entity
#IdClass(EmploymentRecordKey.class)
public class EmploymentRecord {
// no-arg constructor
EmploymentRecord() { }
// normal use constructor
public EmploymentRecord(Long personId, Long companyId, Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
this.companyId = companyId;
this.personId = personId;
}
// composite key
#Id
#Column(name = "company_id", nullable = false)
private Long companyId;
#Id
#Column(name = "person_id", nullable = false)
private Long personId;
#Column(name = "start_date")
private Date startDate;
#Column(name = "end_date")
private Date endDate;
#Version
private int versionId;
#Override
public String toString() {
return
" companyId=" + companyId +
" personId=" + personId +
" startDate=" + startDate +
" endDate=" + endDate +
" versionId=" + versionId;
}
// Getters/Setters
}
// Class to wrap the composite key
class EmploymentRecordKey implements Serializable {
private long companyId;
private long personId;
// no arg constructor
EmploymentRecordKey() { }
#Override
public int hashCode() {
return (int) ((int) companyId + personId);
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (!(obj instanceof EmploymentRecordKey)) return false;
EmploymentRecordKey pk = (EmploymentRecordKey) obj;
return pk.companyId == companyId && pk.personId == personId;
}
// Getters/Setters
}
MySql script, createTables.sql:
DROP TABLE IF EXISTS `test`.`company_person`;
DROP TABLE IF EXISTS `test`.`employment_record`;
DROP TABLE IF EXISTS `test`.`company`;
DROP TABLE IF EXISTS `test`.`person`;
CREATE TABLE `company` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL DEFAULT '',
`address` varchar(500) DEFAULT '',
`version_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `person` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL DEFAULT '',
`address` varchar(500) DEFAULT '',
`version_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* Join table */
CREATE TABLE `company_person` (
`company_id` int NOT NULL,
`person_id` int NOT NULL,
PRIMARY KEY (`person_id`,`company_id`),
KEY `company_idx` (`company_id`),
KEY `person_idx` (`person_id`),
CONSTRAINT `fk_person` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* Employment records */
CREATE TABLE `employment_record` (
`company_id` int NOT NULL,
`person_id` int NOT NULL,
`start_date` datetime,
`end_date` datetime,
`version_id` int NOT NULL,
PRIMARY KEY (`person_id`,`company_id`),
KEY `empl_company_idx` (`company_id`),
KEY `empl_person_idx` (`person_id`),
CONSTRAINT `fk_empl_person` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_empl_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have previous experience in hibernate JPA but not spring JPA. From that knowledge following query might be useful:
select cp.person from CompanyEmployee cp where cp.company.id = ?
You shouldn't need to make a separate entity for the relationship table.
The relationship can be maintained within the two entities,
so if A and B are in a many-to-many relationship,
#Entity
class A {
#Id
Long id;
...
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="a_b",
joinColumns={#JoinColumn(name="id_a", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="id_b", referencedColumnName="id")})
List<B> bList;
...
}
#Entity
class B {
#Id
Long id;
...
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="a_b",
joinColumns={#JoinColumn(name="id_b", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="id_a", referencedColumnName="id")})
List<A> aList;
...
}
You can now use the repository queries on either of the entity repositories or if you have a query with params on both, you can create a custom query in the repository of one.

What is the best way to map data of junction table in Java entity?

I have three tables which presents my very simple project.
CREATE TABLE company (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(250) NOT NULL
);
CREATE TABLE employee (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(250) NOT NULL
);
CREATE TABLE company_employee (
company_id INT NOT NULL,
employee_id INT NOT NULL,
hire_date DATE DEFAULT NULL,
resign_date DATE DEFAULT NULL,
FOREIGN KEY (company_id) REFERENCES company (id),
FOREIGN KEY (employee_id) REFERENCES employee (id)
);
And I have Java representation
#Entity
#Table(name = "employee")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#Column(name = "name")
String name;
#ManyToMany(mappedBy = "employees")
private List<Company> companies;
#Entity
#Table(name = "company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "company_employee",
joinColumns = {#JoinColumn(name = "company_id")},
inverseJoinColumns = {#JoinColumn(name = "employee_id")})
private Collection<Employee> employees;
I can't figured out where I can store history data. In history data I need to store hire_date, resign_date of emplyee and to store all companies of each emplyee as well. So my question is haw can I manage such infortation and what is the best way to store all that history info?
That is a many to many relationship with attributes. I am sure you will be able to understand who to deal with it with one basic example:
http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/
You have already an intersection table "company_employee".
You can store here employee1 1.1.2013 to 31.12.2013 # company1. And an other time 1.1.2014 ... 31.12.2014 # company2.
You have to order by hire_date desc. Per defenition the first value in the list is the current or last employment and all other are consindert as history.
It's not a matter of where to store but how to read the data.

Categories