How to correctly do a manytomany join table in JPA? - jpa

I need 3 entities: User, Contract (which are a many to many relation) and a middle entity: UserContract (this is needed to store some fields).
What I want to know is the correct way to define the relationships between these entities in JPA/EJB 3.0 so that the operations (persist, delete, etc) are OK.
For example, I want to create a User and its contracts and persist them in a easy way.
Currently what I have is this:
In User.java:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserContract> userContract;
In Contract.java:
#OneToMany(mappedBy = "contract", fetch = FetchType.LAZY)
private Collection<UserContract> userContract;
And my UserContract.java:
#Entity
public class UserContract {
#EmbeddedId
private UserContractPK userContractPK;
#ManyToOne(optional = false)
private User user;
#ManyToOne(optional = false)
private Contract contract;
And my UserContractPK:
#Embeddable
public class UserContractPK implements Serializable {
#Column(nullable = false)
private long idContract;
#Column(nullable = false)
private String email;
Is this the best way to achieve my goals?

Everything looks right. My advice is to use #MappedSuperclass on top of #EmbeddedId:
#MappedSuperclass
public abstract class ModelBaseRelationship implements Serializable {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
}
protected Id id = new Id();
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
I omitted obvious constructors/setters for readability. Then you can define UserContract as
#Entity
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "user_id")),
#AttributeOverride(name = "entityId2", column = #Column(name = "contract_id"))
})
public class UserContract extends ModelBaseRelationship {
That way you can share primary key implementation for other many-to-many join entities like UserContract.

Related

Spring Data JPA #OneToOne mapping is not projected

This question is already phrased as an issue here: https://github.com/spring-projects/spring-data-jpa/issues/2369 but for lack of a reaction there I am copying the contents of that issue here, hoping that somebody might find what's wrong with my code or confirm that this could be a bug:
I've set up an example project here that showcases what seems to be a bug in Spring Data projections: https://github.com/joheb-mohemian/gs-accessing-data-jpa/tree/primary-key-join-column-projection-bug/complete
I have a Customer entity that has a OneToOne mapping to an Address entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//...
}
#Entity
public class Address {
#Id
#Column(name = "customer_id")
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "customer_id")
private Customer customer;
private String street;
//...
}
Then there are simple projection interfaces:
public interface CustomerProjection {
String getFirstName();
String getLastName();
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreet();
}
But when I try to fetch a projected entity from a repository method like this one:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
//...
<T> T findById(long id, Class<T> type);
}
, getAddress() on the projection will be null, whereas getAddress() when fetching the entity type is populated correctly. Of these two unit tests, only testEntityWithOneToOne()will be successful:
#BeforeEach
void setUpData() {
customer = new Customer("first", "last");
Address address = new Address(customer, "street");
customer.setAddress(address);
entityManager.persist(address);
entityManager.persist(customer);
}
#Test
void testEntityWithOneToOne() {
Customer customerEntity = customers.findById(customer.getId().longValue());
assertThat(customerEntity.getAddress()).isNotNull();
}
#Test
void testProjectionWithOneToOne() {
CustomerProjection customerProjection = customers.findById(customer.getId(), CustomerProjection.class);
assertThat(customerProjection.getAddress()).isNotNull();
}
What's the problem here?

JPA composite Foreign Key part of composite Primary Key unable to find ID

We have tables,
'Lin_Code_Groups' with fields,
Project_ID (PK),
CG_ID(PK),
CG_Name
Corresponding entity class,
public class Lin_Code_Groups implements Serializable {
#EmbeddedId
private LinCodeGroupPK pk;
private String CG_name;
#Embeddable
public static class LinCodeGroupPK implements Serializable {
private Integer Project_ID;
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer CG_ID;
}
#OneToMany(mappedBy = "lin_Code_Groups")
private List<Lin_CG_Params> lin_CG_Params;
}
table Lin_CG_Params with fields,
Project_ID (PK)..FK to Lin_Code_Groups,
CG_ID(PK)...FK to Lin_Code_Groups,
Param_name(PK),
Param_value
Corresponding entity class,
public class Lin_CG_Params implements Serializable {
#EmbeddedId
private LinCodeGroupParamPK pk;
private String Param_value;
#Embeddable
public static class LinCodeGroupParamPK implements Serializable {
private String Param_name;
private LinCodeGroupPK linCodeGroupPK;
}
#MapsId("linCodeGroupPK")
#ManyToOne
#JoinColumns( {
#JoinColumn(name = "Project_ID",referencedColumnName= "Project_ID"),
#JoinColumn(name = "CG_ID",referencedColumnName= "CG_ID")
})
private Lin_Code_Groups lin_Code_Groups;
}
in controller class, i am using JPA's .Save method to save the data in to the tables.
#PostMapping(value = {"/hello"}, consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> saveNewCodeGroupsDetails(#RequestBody Lin_Code_Groups objLin_Code_Groups ) {
respository.save(objLin_Code_Groups);
}
but getting an error 'Unable to find Lin_CG_Params with id Lin_CG_Params.LinCodeGroupParamPK'
Can anyone is please help ?

There is no ID defined for this entity hierarchy

I am stuck with this error message, that appears every time I want to add a ManytoOne relationship with another entity class.
The class must use a consistent access type (either field or property). There is no ID defined for this entity hierarchy
This is my entity Transaction
#Entity
#Table(name = "CustomerTransaction")
public class CustomerTransaction implements Serializable {//this is the line with the error message
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne //This generates the problem
#JoinColumns({
#JoinColumn(name = "CUS_ID", referencedColumnName = "IDCUSTOMER") })
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private long transactionID;
#Temporal(TemporalType.TIMESTAMP)
private Date buyDate;
public Date getBuyDate() {
return buyDate;
}
public void setBuyDate(Date buyDate) {
this.buyDate = buyDate;
}
public long getTransactionID() {
return transactionID;
}
public void setTransactionID(long transactionID) {
this.transactionID = transactionID;
}
public String getCarYear() {
return carYear;
}
public void setCarYear(String carYear) {
this.carYear = carYear;
}
public Date getTransactionDate() {
return transactionDate;
}
public void setTransactionDate(Date transactionDate) {
this.transactionDate = transactionDate;
}
private String carYear;
#Temporal(TemporalType.TIMESTAMP)
private Date transactionDate;
JPA annotation should all be placed either on fields or on accessor methods. You've placed the #Id and #GeneratedValue annotation on a field (private Long id), but #ManyToOne and #JoinColumns on a getter (public Long getId()). Move the latter on a field as well.
i had similar error but in the end, i realized #Id was referencing this package org.springframework.data.annotation.Id instead of javax.persistence.Id. i was using #MappedSuperClass approach so as soon as i corrected this, everything worked fine
You need to import #Id from "import javax.persistence.Id;"

JPA OneToOne not working

I followed by tutorial : http://www.codejava.net/frameworks/hibernate/hibernate-one-to-one-mapping-with-foreign-key-annotations-example
I have following code:
#Entity
#Table(name = DomainConstant.TABLE_USER)
public class User{
#Id
#Column(name = DomainConstant.DOMAIN_USER_ID)
#GeneratedValue
private Long userId;
private UserActivationCode userActivationCode;
///////////////////// CONSTRUCTOR....
/// STANDARD GET AND SET....
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = DomainConstant.DOMAIN_ACTIVATION_LINK_ID)
public UserActivationCode getUserActivationCode() {
return userActivationCode;
}
}
#Entity
#Table(name = DomainConstant.TABLE_USER_ACTIVATON_LINK)
public class UserActivationCode {
#Id
#Column(name = DomainConstant.DOMAIN_ACTIVATION_LINK_ID)
#GeneratedValue
private Long userActivationCodeId;
#Column(name = DomainConstant.DOMAIN_ACTIVATION_DATE)
#Temporal(javax.persistence.TemporalType.DATE)
private Date date;
#Column(name = DomainConstant.DOMAIN_ACTIVATION_CODE)
private String code;
///////////////////// CONSTRUCTOR....
/// STANDARD GET AND SET....
}
When I save the User object it does not make record in UserActivationCode, why?
Like this:
User newUser = new User();
newUser.setUserActivationCode(new UserActivationCode("this is example"));
userDao.save(newUser);
I have record only in user table.
Can you tell me why?
Your problem is that you are mixing access types. In the User entity you have specified #Id on a field (private Long userId) whereas you have defined the join mapping on a property (the getter to UserActivationCode). If you specify the join mapping on the field, it should work as is.
#Entity
#Table(name = DomainConstant.TABLE_USER)
public class User{
#Id
#Column(name = DomainConstant.DOMAIN_USER_ID)
#GeneratedValue
private Long userId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = DomainConstant.DOMAIN_ACTIVATION_LINK_ID)
private UserActivationCode userActivationCode;
///////////////////// CONSTRUCTOR....
/// STANDARD GET AND SET....
public UserActivationCode getUserActivationCode() {
return userActivationCode;
}
}
For more information on access and access types, see Access, Java EE 7

Seeing "referencedColumnNames(ID) ... not mapped to a single property" error with a 1-M relationship after adding a composite key to the "1" side

I have an existing JPA entity ("Reference") with an ID column as its primary key that it inherits from a base class "BaseEntity" (using the #MappedSuperclass annotation on the superclass).
I also have a 1-M relationship between a Reference and another entity called Violation. Violation was previously defined with a foreign key "REFERENCE_ID" to the "ID" column of the Reference entity.
Recently, I tried to add an unrelated composite key to the Reference entity. This should not have affected the 1-M relationship between Reference and Violation. However, when I run the code in my tomcat server, I see the following stack trace:
Caused by: org.hibernate.AnnotationException: referencedColumnNames(ID) of org.qcri.copydetection.sdk.metastore.entity.Violation.reference referencing org.qcri.copydetection.sdk.metastore.entity.Reference not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:205) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:110) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.processEndOfQueue(AnnotationConfiguration.java:541) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.processFkSecondPassInOrder(AnnotationConfiguration.java:523) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:380) ~[hibernate-annotations-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1206) ~[hibernate-core-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1459) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1086) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:685) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:73) ~[hibernate-entitymanager-3.5.6-Final.jar:3.5.6-Final]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:268) ~[spring-orm-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310) ~[spring-orm-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]
... 39 common frames omitted
Here is the code for the 3 classes involved:
#Entity
#Table(name = "REFERENCE")
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#IdClass(Reference.ContextualName.class)
public class Reference extends BaseEntity {
#Column(name= "LOCATION", unique=true)
#XmlElement
private String location;
#Id
#AttributeOverrides({
#AttributeOverride(name = "name", column = #Column(name = "NAME")),
#AttributeOverride(name = "account", column = #Column(name = "ACCOUNT_ID"))
})
#Column(name = "NAME")
#XmlElement
private String name;
#ManyToOne(optional=false)
#XmlTransient
#JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID")
private Account account;
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Reference() {}
public Reference(String name) {
setName(name);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public Account getAccount() {
return this.account;
}
public void setAccount(Account account) {
this.account = account;
}
#Embeddable
private class ContextualName implements Serializable {
private static final long serialVersionUID = -3687389984589209378L;
#Basic(optional = false)
#Column(name = "NAME")
#XmlElement
private String name;
#ManyToOne(optional=false)
#XmlTransient
#JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID")
private Account account;
ContextualName() {}
}
}
#MappedSuperclass
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
#XmlElement
private Long id;
#Basic(optional = true)
#Column(name = "CREATED", insertable = false, updatable = false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
#XmlElement
private Date creationDate;
protected BaseEntity() {}
public Long getId() {
return id;
}
public void setId(Long id) {
if(this.id==null) {
this.id = id;
} else if (this.id!=id) {
throw new IllegalArgumentException("Cannot change the id after it has been set, as it is a generated field.");
}
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
if(this.creationDate==null) {
this.creationDate = creationDate;
} else if (this.creationDate!=creationDate) {
throw new IllegalArgumentException("Cannot change the creation-date after it has been set, as it is a generated field.");
}
}
}
#Entity
#Table(name = "VIOLATION")
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Violation extends BaseEntity {
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#JoinColumn(name = "REFERENCE_ID", referencedColumnName = "ID")
private Reference reference;
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#JoinColumn(name = "SUSPECT_ID", referencedColumnName = "ID")
private Suspect suspect;
#ManyToOne (optional=false, fetch= FetchType.EAGER)
#XmlTransient
#JoinColumn(name = "SEARCH_ID", referencedColumnName = "ID")
private Search search;
#Basic(optional = false)
#Column(name = "SCORE")
#XmlElement
private double score;
public Violation() {}
public Violation(Search search, Reference ref, Suspect sus, double score) {
this.search = search;
this.reference = ref;
this.suspect = sus;
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Reference getReference() {
return reference;
}
public void setReference(Reference reference) {
this.reference = reference;
}
public Suspect getSuspect() {
return suspect;
}
public void setSuspect(Suspect suspect) {
this.suspect = suspect;
}
public Search getSearch() {
return search;
}
public void setSearch(Search search) {
if(this.search!=null && this.search!=search) {
this.search.removeViolation(this);
}
this.search = search;
if(search!=null) {
if(!search.getViolations().contains(this)) {
search.addViolation(this);
}
}
}
}
To cut a long story short, I'm totally confused how to go about adding a composite key to an existing (legacy) entity that already has an ID column. I can't remove the ID column, nor can I change the 1-M relationship between Reference and Violation. I can't for the life of me understand the error message because the "REFERENCE_ID" foreign key column of the Violation entity is being mapped to a single "ID" column of the Reference entity.
Many thanks in advance!