Spring Data JPA #OneToOne mapping is not projected - spring-data-jpa

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?

Related

#Transactional in spring JPA

I have a spring boot application where I need to update a migratedCustomer db table based on userId and phoneNumber.
Since I have to use for loop in the service layer for every update, it is creating a
new transaction and performance is hampered.
how could I make sure only one transaction is created and hence to improve the performance. code is like below
#Entity
#Table(name = "MigratedCustomer")
public class MigratedCustomer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userId;
private String phoneNumber;
#Temporal(TemporalType.TIMESTAMP)
private Date createdTimestamp;
private int batchNumber;
private String comment;
}
public class MigratedCustomerService {
#Autowired
private UserRepository userRepository;
public void updateMsisdn(List<MigratedCustomer> savedCustomers) {
for (MigratedCustomer savedCustomer : savedCustomers) {
userRepository.updateStatus(savedCustomer.getUserId(),
savedCustomer.getPhoneNumber());
}
}
}
public interface MsisdnRepository extends JpaRepository<Msisdn, Long> {
#Modifying
#Query(value = "UPDATE Msisdn SET status=INACTIVE where userId=:userId and phoneNumber=:phoneNumber",
nativeQuery = true)
void updateStatus(#Param("userId") String userId, #Param("phoneNumber") String phoneNumber);
}

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;"

Spring Data JPA auditing fails when persisting detached entity

I've setup JPA auditing with Spring Data JPA AuditingEntityListener and AuditorAware bean. What I want is to be able to persist auditor details even on entities with predefined identifiers.
The problem is that when JPA entity with predefined id is being persisted and flushed it's auditor details cannot be persisted:
object references an unsaved transient instance - save the transient instance before flushing: me.auditing.dao.AuditorDetails
The interesting part is that when an entity with a generated id is saved - everything's fine. In both cases the entities are new. I could not pinpoint the problem digging through hibernate code so I've created a sample project to demonstrate this (test class me.auditing.dao.AuditedEntityIntegrationTest) It has both entities with predefined and generated identifiers and should be audited.
The entities are:
#Entity
public class AuditedEntityWithPredefinedId extends AuditableEntity {
#Id
private String id;
public String getId() {
return id;
}
public AuditedEntityWithPredefinedId setId(String id) {
this.id = id;
return this;
}
}
and:
#Entity
public class AuditedEntityWithGeneratedId extends AuditableEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
public String getId() {
return id;
}
public AuditedEntityWithGeneratedId setId(String id) {
this.id = id;
return this;
}
}
where parent class is:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity implements Serializable {
private static final long serialVersionUID = -7541732975935355789L;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#CreatedBy
private AuditorDetails createdBy;
#CreatedDate
private LocalDateTime createdDate;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#LastModifiedBy
private AuditorDetails modifiedBy;
#LastModifiedDate
private LocalDateTime modifiedDate;
And the auditor getter implementation is:
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
}
Edit 2016-08-08: It seems that when a new entity with predefined id is saved, it gets two different instances of createdBy and modifiedBy AuditorDetails, which is quite logical if the entity wouldn't be actually new. So, a completely new entity with generated gets both AuditorDetails of same instance, and the one with manually set id doesn't. I tested it by saving auditor details in AuditorAware bean before returning it to AuditingHandler.
Ok, so for now the only solution I could find is to actually persist AuditorDetails before writing it to audited entities like so:
#Override
#Transactional
public AuditorDetails getCurrentAuditor() {
AuditorDetails details = new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
return auditorDetailsRepository.save(details);
}
It is not the most elegant solution, but it works for now.

Error on em.getTransaction().commit(); using the JPA #Embeddable annotation

I have some problems with #Embeddable in JAVA JPA.
I have an entity class named "Author":
#Entity
#Table(name = "author")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Author.findAll", query = "SELECT a FROM Author a"),
...})
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "aID")
private Integer aID;
#Column(name = "aName")
private String aName;
#Column(name = "aSurname")
private String aSurname;
#Column(name = "aPhone")
private Integer aPhone;
#Embedded
#AttributeOverrides({
#AttributeOverride(name="city",column=#Column(name="Address")),
#AttributeOverride(name="street",column=#Column(table="Address")),
#AttributeOverride(name="number",column=#Column(table="Address"))
}) private Address address;
// set and get methods.
}
Also I have an Embeddable class named "Address":
#Embeddable
#Table(name = "Address")
#XmlRootElement
public class Address implements Serializable
{
private static final long serialVersionUID=1L;
#Column(name="city")
private String city;
#Column(name="street")
private String street;
#Column(name="number")
private int number;
// get and set methods.
}
In my main class I want to insert this values to the database. (I use mySQL) But I am getting an error on this line: em.getTransaction.commit();
public class CreateAuthor extends javax.swing.JFrame {
private static final String PERSISTENCE_UNIT_NAME = "Project";
private static EntityManagerFactory emf;
public void CreateAuthor() {
initComponents();
}
private void ekleButtonActionPerformed(java.awt.event.ActionEvent evt) {
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author author = new Author();
author.setAID(3);
author.setAName("Sheldon");
author.setASurname("Smith");
author.setAPhone(768987);
Address adr = new Address();
adr.setCity("Paris");
adr.setStreet("cinar");
adr.setNumber(12);
author.setAddress(adr);
em.persist(author);
em.getTransaction().commit(); /// error occured
em.close();
}
}
On my database side, I have Author table (aID(pk),aName,aSurname,aPhone)
Address Table (city,street,number)
Do you have any idea why an error is occured?
The goal of Embeddable is to have fields of an object (Address) stored in the same table as the entity's table (Author -> author).
If you want to save them in another table, than Address should be an entity on its own, and there should be a OneToOne or ManyToOne association between Author and Address. The mapping, as is, don't make any sense.

How to correctly do a manytomany join table in 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.