Duplicate Entry Exception: Spring Hibernate/JPA cascade save Many To One - java

It is a spring application (no spring boot).
The database I am using is MySQL.
The issue I am having is when saving the entity Driver which has a Many to one relationship on both Carrier and Location.
What I want to do is, when I do the save on Driver. Driver along with Location and Carrier is persisted to the database. The issue I am having is when trying to save. I get duplicate key violation
Stack trace:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1062, SQLState: 23000
Feb 18, 2019 1:25:42 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [UK_lheij6i9eldhfhyu9j1q5fjls]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:296)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy47.saveAll(Unknown Source)
at greyhound.service.GreyhoundServiceImpl.process(GreyhoundServiceImpl.java:38)
at greyhound.Main.main(Main.java:17)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3073)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3666)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:332)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:289)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:828)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:795)
at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:428)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:266)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308)
at com.sun.proxy.$Proxy44.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:489)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:521)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 11 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:970)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1109)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1057)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1377)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1042)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
... 69 more
Process finished with exit code 1
Entity/Model classes: (Have removed getters/setters)
#Entity
#Table(name = "Driver")
public class Driver {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
#Column(name = "version")
private int version;
#Column(name = "driver_id")
private Long driverId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "middle_init")
private String middleInitial;
#ManyToOne(fetch = FetchType.EAGER)
#Cascade({CascadeType.ALL})
private Carrier carrier;
#ManyToOne(fetch = FetchType.EAGER)
#Cascade({CascadeType.ALL})
private Location location;
#Entity
#Table(name="Carrier")
public class Carrier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
#Column(name = "version")
private int version;
#PrimaryKeyJoinColumn
#Column(name = "carrier_name")
private String carrierName;
#OneToMany
#JoinColumn(name = "carrier_id", referencedColumnName = "id")
#Entity
#Table(name="Locations")
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Version
private Long version;
#Column(name = "location_id")
private Long locationId;
#Column(name = "location_name")
private String locationName;
#OneToMany
#JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List<Driver> drivers = new ArrayList<Driver>();
}
Code preparing the entities
private List<Driver> prepareEntityList(Result result) {
List<Driver> drivers = new ArrayList<Driver>();
for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
Location location = new Location();
location.setLocationName(driverAssignment.getHomeLocation3());
location.setLocationId(driverAssignment.getHomeLocation());
Carrier carrier = new Carrier();
carrier.setCarrierName(driverAssignment.getCarrierId());
Driver driver = new Driver();
driver.setDriverId(driverAssignment.getDriverId());
driver.setFirstName(driverAssignment.getFirstName());
driver.setLastName(driverAssignment.getLastName());
driver.setMiddleInitial(driverAssignment.getMiddleInitial());
driver.setCarrier(carrier);
driver.setLocation(location);
drivers.add(driver);
}
return drivers;
}
Question: is it possible to achieve what I am trying to do? Expect hibernate to handle the relationships when I try to save and associate a location with a driver if it has already been saved instead of trying to save it again.
If not, what is a suggested approach to save these entities?
Datasource configuration
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "greyhound" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/greyhound1");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
Update #2
Have a DriverRepository like this
#Repository
public interface DriverRepository extends JpaRepository<Driver, Long> {
}
To save:
repository.saveAll(drivers);
Github link
https://github.com/mukulgoel1989/greyhound
I have added the github link in case someone is willing to give this a try.

I prepared the working solution: Cepr0/greyhound-demo.
I reworked your project "a little" - did it with Spring-Boot, Lombok and H2 database, just for demo purposes and to simplify it.
So, if I'm not mistaken, the task is to transform 'assignments' (from the greyhound site):
{
"results": [
{
"oper_nbr": 1,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "SMITH",
"middle_init": null,
"home_loc_6": 12345,
"home_loc_3": "NLX",
"oper_class": "T"
},
{
"oper_nbr": 2,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "DOE",
"middle_init": null,
"home_loc_6": 67890,
"home_loc_3": "NLX",
"oper_class": "T"
}
]
}
to three entities: Driver, Location, and Carrier with the relations:
Location -1---*- Driver -*---1- Carrier
i.e. Driver has 'many-to-one' relation with Location and Carrier.
The main problem of this task is that while saving the Driver entity, we need to use the already persisted Location and Carrier entities, or use new ones. So to solve it we have to:
Prepare 3 repositories for those entities.
For each 'assignment' find related Location and Carrier.
If Location and Carrier are not found then create new ones.
Create a new Driver and set the found Location and Carrier or new ones having been created.
Persist the Driver (and cascaded persist Location and Carrier if they are not found).
The final code of method GreyhoundService.process():
#Transactional
public void process() {
client.getAssignments()
.stream()
.forEach(a -> {
log.debug("[d] Assignment: {}", a);
Driver driver = new Driver();
driver.setId(a.getDriverId());
driver.setFirstName(a.getFirstName());
driver.setLastName(a.getLastName());
driver.setMiddleName(a.getMiddleName());
driver.setLocation(
locationRepo.findById(new Location.PK(a.getLocationId(), a.getLocationName()))
.orElse(new Location(a.getLocationId(), a.getLocationName()))
);
driver.setCarrier(
carrierRepo.findById(a.getCarrierId().trim())
.orElse(new Carrier(a.getCarrierId().trim()))
);
driverRepo.saveAndFlush(driver);
log.debug("[d] Driver: {}", driver);
});
}
To minimize the size of the data in the database and the number of SQL selects I transformed the initial entities as follows:
Driver
#Getter
#Setter
#ToString
#EqualsAndHashCode(of = "id")
#Entity
#Table(name = "drivers")
public class Driver implements Persistable<Long> {
#Id private Long id;
private String firstName;
private String lastName;
private String middleName;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "carrierId", foreignKey = #ForeignKey(name = "drivers_carriers"))
private Carrier carrier;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumns(
value = {#JoinColumn(name = "locationId"), #JoinColumn(name = "locationName")},
foreignKey = #ForeignKey(name = "drivers_locations")
)
private Location location;
#Override
public boolean isNew() {
return true;
}
}
Location
#Data
#NoArgsConstructor
#Entity
#Table(name = "locations")
#IdClass(Location.PK.class )
public class Location {
#Id private Long locationId;
#Id private String locationName;
public PK getId() {
return new PK(locationId, locationName);
}
public void setId(PK id) {
this.locationId = id.getLocationId();
this.locationName = id.getLocationName();
}
public Location(final Long locationId, final String locationName) {
this.locationId = locationId;
this.locationName = locationName;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class PK implements Serializable {
private Long locationId;
private String locationName;
}
}
Carrier
#Data
#NoArgsConstructor
#Entity
#Table(name = "carriers")
public class Carrier {
#Id private String carrierId;
public Carrier(final String carrierId) {
this.carrierId = carrierId;
}
}
As you can see I used natural identifiers for Location and Carrier (and a composite one in Carrier). This made it possible not only to reduce the size of the data but also reduce the number of additional SQL queries that Hibernate performs when storing complex entities. When Location and Carrier tables are filled, Hibernate does not perform unnecessary queries to find them but takes their data from its own cache (you can see this in the app log).
P.S. Note that this solution is not optimal. IMO to make it better you can split the main process into two parts: the first one persists distinct Locations and Carriers and the second one just persists Drivers without finding Locations and Carriers. Both parts perform with batch insert.
UPDATE
Branch with the optimal solution: Cepr0/greyhound-demo:async_and_batch_insert
Due to the asynchronous persisting of Locations and Carriers and with batch insert, processing takes only about 5 seconds.

duplicate problem is causing when it goes to insert entries for both sides.
The solution is to mark one side as 'mappedby' the other.
so use in Carrier Class:
#OneToMany(mappedBy="carrier")
#JoinColumn(name = "carrier_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();
And in Location Class:
#OneToMany(mappedBy="location")
#JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List<Driver> drivers = new ArrayList<Driver>();

Since Location and Carrier are versioned and you don't set the version in the instances you create, Hibernate probably just considers them new and tries to insert them (otherwise, if they'd need to be updated, Hibernate would anyway not know which versions to compare since it's missing in the updated instances).
Firstly, you need either to:
fetch existing Location and Carrier instances from db and update them if they're not new (you know that by checking if id is set);
or properly propagate and set version attribute as well together with id and business attributes.
Secondly, you also have two choices with your current entity mappings (and the stated goal to let Hibernate save the entire graph properly):
if you don't use transactions, then for both of the above you have to resort to entityManager.merge(driver) so that everything is properly either inserted or updated;
otherwise and only if you pick the first option above and read existing Location and Carrier instances and call repository.saveAll(drivers) in the same transaction, then it would work, because driver instances would be persisted and PERSIST operation would be cascaded to still attached location and carrier instances.
There are (many) other possibilities depending on the architectural choices and conventions used in you project, i.e. I would never cascade ALL from many side to one side (an example undesired consequence is removal), and I would in most cases always explicitly save the one side separately, but it's up to you.

One way to resolve this would be to just load all the locations and carriers so with a findById() this way they are bound to you session. Use these obtained objects to be set on you new driver objects. This should resolve the issue.
for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
Location location;
if (driverAssignment.getHomeLocation() != null) {
location = locationRepo.findById(driverAssignment.getHomeLocation());
} else {
location = new Location();
}
location.setLocationName(driverAssignment.getHomeLocation3());
Carrier carrier;
if (driverAssignment.getCarrierId() != null) {
carrier = carrierRepo.findById(driverAssignment.getCarrierId());
} else {
carrier = new Carrier();
}
Driver driver;
if (driverAssignment.getDriverId() != null) {
driver = driverRepo.findById(driverAssignment.getCarrierId());
} else {
driver = new Driver();
}
driver.setDriverId(driverAssignment.getDriverId());
driver.setFirstName(driverAssignment.getFirstName());
driver.setLastName(driverAssignment.getLastName());
driver.setMiddleInitial(driverAssignment.getMiddleInitial());
driver.setCarrier(carrier);
driver.setLocation(location);
drivers.add(driver);
}
It would look something like this. This is just a simple example because I don't know the actual context.

You also need to populate Carrier and Location with the crated Driver:
for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
Location location = new Location();
Carrier carrier = new Carrier();
Driver driver = new Driver();
driver.setCarrier(carrier);
driver.setLocation(location);
// add this
location.getDrivers().add(driver);
carrier.getDrivers().add(driver);
drivers.add(driver);
}
this needs to be done due to the bi-directional mapping you used (#OneToMany)
Update:
Use JPA cascade config, not Hibernate one:
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Carrier carrier;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Location location;
Update 2
After taking closer look at your project it seems that you are missing a transactional set-up on:
#Transactional
public void process() {
inside that method you are performing many repository operations which is fine as all SimpleJpaRepository methods are transactional.
The problem in my opinion is that not all of these operations are run under same transaction and effectively persistence context. (each operations runs within its own small transaction and after that all the entities are detached from the persistence context).
NOTE: you may need to play with configuration a bit to enable #Transactional annotation set-up.

We need to pass the identifier as a referenced column name. Map the Location entity like below.
#OneToMany
#JoinColumn(name = "location_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();

Related

JpaObjectRetrievalFailureException when saving entity with one-to-many and client-assigned ids

In a simple Spring Boot Application, I'm facing with a JpaObjectRetrievalFailureException when I'm trying to save an entity with one-to-many association and client-assigned ids.
Please take a look on these entities and on this simple repository:
#Entity
#Table(name = "cart")
public class Cart {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "cart_id")
private List<Item> items;
// constructors, getters, setters, equals and hashCode ommitted
}
#Entity
#Table(name = "item")
public class Item {
#Id
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
// constructors, getters, setters, equals and hashCode ommitted
}
public interface CartRepository extends JpaRepository<Cart, UUID> {
}
I wrote this test:
#DataJpaTest
class CartRepositoryTest {
#Autowired
private CartRepository cartRepository;
#Test
void should_save_cart() {
// GIVEN
final var cart = new Cart(UUID.randomUUID(), "cart");
final var item = new Item(UUID.randomUUID(), "item");
cart.setItems(List.of(item));
// WHEN
final var saved = cartRepository.save(cart);
// THEN
final var fetched = cartRepository.findById(saved.id());
assertThat(fetched).isPresent();
}
}
When I run the test, call to cartRepository.save(cart) fails with:
Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.testjpaonetomany.domain.Item with id f5658508-f3d0-4d9b-a1f0-17b614753356
at app//org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:379)
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
at app//org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at app//org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at app//org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at app/jdk.proxy3/jdk.proxy3.$Proxy105.save(Unknown Source)
at app//com.example.testjpaonetomany.repository.CartRepositoryTest.should_save_cart(CartRepositoryTest.java:28)
If I modify my entities by adding #GeneratedValue for ids, and in my test, I replace UUID.randomUUID() by null to delegate to Hibernate the ID generation, the test passes.
How to deal with client-generated ids?
The cause is that you save the parent object only (which is absolutely correct and fine) but still need to explain JPA that the operation should be propagated i.e.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cart_id")
private List<Item> items;
As minor improvements I would suggest to put the UUID generation into constructors and establish the relation via the dedicated method i.e.
final var cart = new Cart("cart");
cart.addItem(new Item("item"));
and probably consider using em.persist() instead of repository.save() as it makes a select request first in case of using uuids as #Augusto mentioned

How to avoid LazyInitializationException with nested collections in JPA-Hibernate?

Mandatory background info:
As part of my studies to learn Spring, I built my usual app - a little tool that saves questions and later creates randomized quizzes using them.
Each subject can have any number of topics, which in turn may have any number of questions, which once again in turn may have any number of answers.
Now, the problem proper:
I keep getting LazyInitializationExceptions.
What I tried last:
I changed almost each and every collection type used to Sets.
Also felt tempted to set the enable_lazy_load_no_trans property to true, but I've consistently read this is an antipattern to avoid.
The entities proper: (only fields shown to avoid wall of code-induced fatigue)
Subject:
#Entity
#Table(name = Resources.TABLE_SUBJECTS)
public class Subject implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_SUBJECT)
private int subjectId;
#Column(name="subject_name", nullable = false)
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_SUBJECT,
fetch = FetchType.EAGER
)
private Set<Topic> topics;
}
Topic:
#Entity
#Table(name = Resources.TABLE_TOPICS)
public class Topic implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "topic_id")
private int topicId;
#Column(name = "name")
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_TOPIC,
orphanRemoval = true,
cascade = CascadeType.MERGE,
fetch = FetchType.EAGER
)
private Set<Question> questions;
#ManyToOne(
fetch = FetchType.LAZY
)
private Subject subject;
}
Question:
#Entity
#Table(name = Resources.TABLE_QUESTIONS)
public class Question implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_QUESTION)
private int questionId;
#Column(name = "statement")
private String statement;
#OneToMany(
mappedBy = Resources.ENTITY_QUESTION,
orphanRemoval = true,
cascade = CascadeType.MERGE
)
private Set<Answer> answers;
#ManyToOne(
fetch = FetchType.LAZY
)
private Topic topic;
}
Answer:
#Entity
#Table(name = Resources.TABLE_ANSWERS)
public class Answer implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_ANSWER)
private int answerId;
#Column(name = "answer_text", nullable = false)
private String text;
#Column(name = "is_correct", nullable = false)
private Boolean isCorrect;
#ManyToOne(
fetch = FetchType.LAZY
)
private Question question;
}
I'm using interfaces extending JpaRepository to perform CRUD operations. I tried this to fetch stuff, without luck:
public interface SubjectRepository extends JpaRepository<Subject, Integer>
{
#Query
Optional<Subject> findByName(String name);
#Query(value = "SELECT DISTINCT s FROM Subject s " +
"LEFT JOIN FETCH s.topics AS t " +
"JOIN FETCH t.questions AS q " +
"JOIN FETCH q.answers as a")
List<Subject> getSubjects();
}
Now, the big chunk of text Spring Boot deigns to throw at me - the stack trace:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [org.callisto.quizmaker.domain.Subject#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.callisto.quizmaker.domain.Subject$HibernateProxy$B8rwBfBD.getTopics(Unknown Source) ~[main/:na]
at org.callisto.quizmaker.service.QuizMakerService.activeSubjectHasTopics(QuizMakerService.java:122) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.checkIfActiveSubjectHasTopics(QuizMaker.java:307) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.createNewQuestion(QuizMaker.java:117) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.prepareMainMenu(QuizMaker.java:88) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.run(QuizMaker.java:65) ~[main/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]
This exception happens when I call this line of code:
boolean output = service.activeSubjectHasTopics();
Which, in turn, calls this method on a service class:
public boolean activeSubjectHasTopics()
{
if (activeSubject == null)
{
throw new NullPointerException(Resources.EXCEPTION_SUBJECT_NULL);
}
return !activeSubject.getTopics().isEmpty();
}
The activeSubjectHasTopics method gets called in this context:
private void createNewQuestion(View view, QuizMakerService service)
{
int subjectId = chooseOrAddSubject(view, service);
service.setActiveSubject(subjectId);
if (checkIfActiveSubjectHasTopics(view, service))
{
chooseOrAddTopic(view, service, subjectId);
}
do
{
createQuestion(view, service);
createAnswers(view, service);
}
while(view.askToCreateAnotherQuestion());
service.saveDataToFile();
prepareMainMenu(view, service);
}
private boolean checkIfActiveSubjectHasTopics(View view, QuizMakerService service)
{
boolean output = service.activeSubjectHasTopics();
if (!output)
{
view.printNoTopicsWarning(service.getActiveSubjectName());
String topicName = readTopicName(view);
createNewTopic(service, topicName);
}
return output;
}
Not helpful if you change your structure to set. If you need to get the entities you need to explicitly include FETCH clause in your hql queries. You'll work your way by checking out the Hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching
I was able to track down the cause of the issue thanks to a comment from Christian Beikov - to quote:
Where do you get this activeSubject object from? If you don't load it
as part of the transaction within activeSubjectHasTopics, then this
won't work as the object is already detached at this point, since it
was loaded through a different transaction.
The activeSubject object was defined as part of the service class containing the activeSubjectHasTopics method, and was initialized by a different transaction as he pointed out.
I was able to fix the problem by annotating that service class as #Transactional and storing the IDs of the objects I need instead of the objects themselves.

H2 ManyToMany populate

I want to poulate ManyToMany table in my H2 database for test. I have Entities with many to many relationship. I know how to insert table "sessions" and table "speakers", but i dont know how to insert table "session_speakers":
#Entity
#Table(name = "sessions")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "session_id")
private Long sessionId;
#Column(name = "session_name")
private String sessionName;
#Column(name = "session_description")
private String sessionDescription;
#Column(name = "session_length")
private Integer sessionLength;
#ManyToMany
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn(name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id"))
private List<Speaker> speakers;
and
#Entity
#Table(name = "speakers")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Speaker {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "speaker_id")
private Long speakerId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "title")
private String title;
#Column(name = "company")
private String company;
#Column(name = "speaker_bio")
private String speakerBio;
#ManyToMany(mappedBy = "speakers")
#JsonIgnore
private List<Session> sessions;
My H2JpaConfig
#Configuration
#EnableTransactionManagement
public class H2JpaConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
.setName("DataBaseTestConfig" + ZonedDateTime.now() + UUID.randomUUID()).build();
}
}
my application-h2.properties:
hibernate.connection.driver_class=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create\r\n
spring.jpa.database=h2
spring.datasource.username=test
spring.datasource.password=password
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.connection.url=jdbc\:h2\:mem\:testdb;;
spring.jpa.generate-ddl=true
and my data.sql for tests:
INSERT INTO sessions (session_name,session_length,session_description)
VALUES ('Keynote - The Golden Age of Software',45,''),
('A Better Way to Access Data with Spring Data',60,''),
('A Deep Dive Into Spring IoC',60,'') ...
INSERT INTO speakers (first_name,last_name,title,company,speaker_bio)
VALUES ('Sergio','Becker','Senior Developer','MicroOcean Software','Test'),
('James','Lowrey','Solutions Architect','Fabrikam Industries','Test') ...
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (1,40),
(2,4),
(3,5)...
when i run my tests i got error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #6 of URL [file:/C:/work/JIRA_work/nauka/spring_project/ps-spring-data-jpa/conference-demo/target/test-classes/data.sql]: INSERT INTO session_speakers (session_id,speaker_id) VALUES (1,40), (2,4), ... nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKBSD81C224TLAEPMSBQIWO3OBG: PUBLIC.SESSION_SPEAKERS FOREIGN KEY(SESSION_ID) REFERENCES PUBLIC.SESSIONS(SESSION_ID) (82)"; SQL statement:
INSERT INTO session_speakers (session_id,speaker_id) VALUES (1,40), (2,4) ...
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at
I would like to know, how to properly populate ManyToMany table.
You are referencing non-existent IDs.
You should either insert IDs manually (not relying on them being generated by the database) in the first two tables and reference those later.
INSERT INTO sessions (speaker_id, session_name,session_length,session_description)
VALUES (1, 'Keynote - The Golden Age of Software',45,''),
(2, 'A Better Way to Access Data with Spring Data',60,''),
(3, 'A Deep Dive Into Spring IoC',60,'') ...
INSERT INTO speakers (session_id, first_name,last_name,title,company,speaker_bio)
VALUES (11, 'Sergio','Becker','Senior Developer','MicroOcean Software','Test'),
(12, 'James','Lowrey','Solutions Architect','Fabrikam Industries','Test') ...
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (1,11),
(2,12),
(3,11)...
Or you can populate the first two tables the way you do it now, but than you have to find the correct IDs when inserting into the junction table.
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (
(SELECT session_id FROM sessions WHERE session_name = 'Keynote - The Golden Age of Software'),
(SELECT speaker_id FROM speakers WHERE last_name = 'Becker'))...
This way may not be the right one in your situation, but there are circumstances where it can be appropriate.

How to persist a new entity containing multiple identical instances of another unpersisted entity with spring-boot and JPA?

Overview:
I'm building a spring-boot application which, in part, retrieves some entities from an external REST service and compares it to previous versions of the entity held locally in a database.
I'm injecting EntityManager with #PersistenceContext, and using that to work with the database, as there are many entity types, and the type is initially unknown to the module. I could get a JpaRepository from a factory, but the number of different entity types is liable to grow, and I'd rather not rely on that if at all possible.
Problem:
When the module retrieves an entity which it doesn't hold in the database, it does some business logic and then tries to persist the new entity.
The Person class, which is one of the entities in question, contains three fields of type Site, which often hold the same objects.
When I try to persist a new Person which has the same Site object in multiple fields with CascadeType.PERSIST, I get an EntityExistsException (see stacktrace (1)).
When I remove the CascadeType.PERSIST from the Site fields, and try to persist a new Person which has the same Site object in multiple fields, I get a TransientPropertyValueException (see stacktrace (2)).
I think I understand the reasons why both exceptions occur:
In the first case it's because after the first site field is cascade-persisted, it cannot be repersisted for the second field.
The second case I think is because the the #Transactional annotation is trying to flush the transaction without the site instance(s) being persisted.
I've tried removing the #Transactional annotation and beginning and commiting an EntityTransaction myself, but I get an IllegalStateException (see stacktrace (3)), though I think this is expected as spring should be handling the transactions itself.
I've looked at answers to similar questions (e.g. this, this) but all suggest some variation of changing the CascadeType.
In another question someone suggested to make sure that the entities in question were being evaluated correctly by the equals() method, so I checked in the debugger, and ((Person)newEntity).currentSite.equals(((Person)newEntity).homeSite) evaluates to true.
How can I go about consistently persisting/merging entities with the same object accross multiple fields?
Edit: I've also tried the various combinations of cascade types with fetch = FetchType.EAGER, but this produces no change in their respective exceptions.
Edit 2: I've tried using a JpaRepository instead of using the EntityManager, and get effectively the same set of exceptions depending on what cascade types I'm using.
If I use PERSIST, but no MERGE, I get an EntityNotFoundException (see stacktrace (4)), and if I use both PERSIST and MERGE I get an InvalidDataAccessApiUsageException` (see stacktrace (5)).
Person:
#EqualsAndHashCode(callSuper = true)
#javax.persistence.Entity
#XmlDiscriminatorValue("person")
#XmlRootElement(name = "person")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Subscriber.class})
public class Person extends MobileResource implements Serializable {
private static final Logger LOG = LogManager.getLogger(Person.class);
private String firstName;
private String surname;
public Person() {
super();
}
public Person(Long id) {
super(id);
}
public Person(Person that) {
super(that);
this.firstName = that.firstName;
this.surname = that.surname;
}
// getters && setters
}
MobileResource:
#EqualsAndHashCode(callSuper = true)
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#XmlRootElement(name = "resource")
#XmlDiscriminatorNode("#type")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Vehicle.class, Person.class})
public abstract class MobileResource extends Resource implements Serializable {
private static final Logger LOG = LogManager.getLogger(MobileResource.class);
#ManyToOne(cascade = CascadeType.ALL)
private MobileResourceStatus status;
private Long incidentId;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
private Site homeSite;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
private Site currentSite;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
private Site relocationSite;
public MobileResource() {
super();
}
public MobileResource(Long id) {
super(id);
}
public MobileResource(MobileResource that) {
super(that);
this.status = that.status;
this.incidentId = that.incidentId;
this.homeSite = that.homeSite;
this.currentSite = that.currentSite;
this.relocationSite = that.relocationSite;
}
// getters && setters
}
Site:
#EqualsAndHashCode(callSuper = true)
#javax.persistence.Entity
public class Site extends Resource implements Serializable {
private static final Logger LOG = LogManager.getLogger(Site.class);
private String location;
public Site() {
super();
}
public Site(Long id) {
super(id);
}
public Site(Site that) {
super(that);
this.location = that.location;
}
}
Resource:
#EqualsAndHashCode
#MappedSuperclass
#XmlRootElement
#XmlSeeAlso({MobileResource.class})
public abstract class Resource implements Entity, Serializable {
private static final Logger LOG = LogManager.getLogger(Resource.class);
#Id
private Long id;
private String callSign;
#XmlPath(".")
private LatLon latLon;
private Long brigadeId;
private Long batchId;
#ManyToMany(cascade = CascadeType.ALL)
private List<Attribute> attributes;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
private ResourceType type;
public Resource() {
}
public Resource(Long id) {
this.id = id;
}
public Resource(Resource that) {
this.id = that.id;
this.callSign = that.callSign;
this.latLon = that.latLon;
this.attributes = that.attributes;
this.batchId = that.batchId;
this.brigadeId = that.brigadeId;
this.type = that.type;
}
// getters && setters
}
DefaultEntityMessageHandler:
#Component
public class DefaultEntityMessageHandler implements EntityMessageHandler {
#PersistenceContext
private EntityManager entityManager;
#Override
#Transactional
public void handleEntityMessage(EntityMessageData data, Message message) {
// business logic
if (newEntity != null) {
if (oldEntity != null)
entityManager.merge(newEntity);
else
entityManager.persist(newEntity);
}
}
}
Stacktrace (1):
2018-06-06 12:05:15,975 ERROR ActiveMQMessageConsumer - ID:cpt-9225-1528283097161-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:4
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [my.class.path.entity.resource.site.Site#738]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:118)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:813)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:773)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:467)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:392)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
...
Changing the cascade type in MobileResource:
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
private Site homeSite;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
private Site currentSite;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
Stacktrace (2):
2018-06-06 12:19:24,084 ERROR ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site]
2018-06-06 12:19:24,093 ERROR ActiveMQMessageConsumer - ID:cpt-9436-1528283955454-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:8
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -> my.class.path.entity.resource.site.Site
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:540)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
...
Stacktrace (3):
2018-06-06 13:29:35,594 ERROR ActiveMQMessageConsumer - ID:cpt-9864-1528288166188-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:9
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:254)
at com.sun.proxy.$Proxy114.getTransaction(Unknown Source)
at my.class.path.entity_controller.DefaultEntityMessageHandler.handleEntityMessage(DefaultEntityMessageHandler.java:60)
at my.class.path.entity_listener.listeners.IdExtractorMessageListener.onMessage(IdExtractorMessageListener.java:41)
...
Stacktrace (4)
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-06-06 15:26:36,143 ERROR SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
Caused by: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find my.class.path.entity.resource.site.Site with id 738; nested exception is javax.persistence.EntityNotFoundException: Unable to find my.class.path.entity.resource.site.Site with id 738
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:373)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
...
Stacktrace (5)
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-06-06 15:31:54,840 ERROR SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy122.save(Unknown Source)
at my.class.path.OfficerSubscription.run(OfficerSubscription.java:81)
at my.class.path.OfficerSubscription$$FastClassBySpringCGLIB$$705870eb.invoke(<generated>)
...
After days of search, I finally solved this in my spring boot project.
Add follwing blocks in the application.yaml file:
spring:
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
event:
merge:
entity_copy_observer: allow

derby + hibernate ConstraintViolationException using manytomany relationships

I'm new to Hibernate+Derby... I've seen this issue mentioned throughout the google, but have not seen a proper resolution.
This following code works fine with mysql, but when I try this on derby i get exceptions:
( each Tag has two sets of files and vise-versa - manytomany)
Tags.java
#Entity
#Table(name="TAGS")
public class Tags implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO)
public long getId()
{
return id;
}
#ManyToMany(targetEntity=Files.class
)
#ForeignKey(name="USER_TAGS_FILES",inverseName="USER_FILES_TAGS")
#JoinTable(name="USERTAGS_FILES",
joinColumns=#JoinColumn(name="TAGS_ID"),
inverseJoinColumns=#JoinColumn(name="FILES_ID"))
public Set<data.Files> getUserFiles()
{
return userFiles;
}
#ManyToMany(mappedBy="autoTags",
targetEntity=data.Files.class)
public Set<data.Files> getAutoFiles()
{
return autoFiles;
}
Files.java
#Entity
#Table(name="FILES")
public class Files implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO)
public long getId()
{
return id;
}
#ManyToMany(mappedBy="userFiles",
targetEntity=data.Tags.class)
public Set getUserTags()
{
return userTags;
}
#ManyToMany(targetEntity=Tags.class
)
#ForeignKey(name="AUTO_FILES_TAGS",inverseName="AUTO_TAGS_FILES")
#JoinTable(name="AUTOTAGS_FILES",
joinColumns=#JoinColumn(name="FILES_ID"),
inverseJoinColumns=#JoinColumn(name="TAGS_ID"))
public Set getAutoTags()
{
return autoTags;
}
I add some data to the DB, but when running over Derby these exception turn up (the don't using mysql)
Exceptions
SEVERE: DELETE on table 'FILES' caused a violation of foreign key constraint 'USER_FILES_TAGS' for key (3). The statement has been rolled back.
Jun 10, 2010 9:49:52 AM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: could not delete: [data.Files#3]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2712)
at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2895)
at org.hibernate.action.EntityDeleteAction.execute(EntityDeleteAction.java:97)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:613)
at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
at $Proxy13.flush(Unknown Source)
at data.HibernateORM.removeFile(HibernateORM.java:285)
at data.DataImp.removeFile(DataImp.java:195)
at booting.DemoBootForTestUntilTestClassesExist.main(DemoBootForTestUntilTestClassesExist.java:62)
I have never used derby before so maybe there is something crutal that i'm missing
1) what am I doing wrong?
2) is there any way of cascading properly when I have 2 many-to-many relationships between two classes?
Thanks!
This following code works fine with MySQL, but when I try this on derby I get exceptions:
My guess is that you weren't using referential integrity with MySQL (i.e. not the InnoDB engine) so the constraint violation wasn't "triggered". But the error is there.
And actually, the problem is that you should put the mappedBy on the same side of the bi-directional associations, for example on Tags for both ManyToMany (and remove it from Files):
#Entity
public class Tags implements Serializable {
#ManyToMany(mappedBy = "userTags")
#ForeignKey(name = "USER_TAGS_FILES", inverseName = "USER_FILES_TAGS")
#JoinTable(name = "USERTAGS_FILES", joinColumns = #JoinColumn(name = "TAGS_ID"), inverseJoinColumns = #JoinColumn(name = "FILES_ID"))
public Set<Files> getUserFiles() {
return userFiles;
}
#ManyToMany(mappedBy = "autoTags")
public Set<Files> getAutoFiles() {
return autoFiles;
}
//...
}
And Files becomes:
#Entity
public class Files implements Serializable {
#ManyToMany
public Set<Tags> getUserTags() {
return userTags;
}
#ManyToMany
#ForeignKey(name = "AUTO_FILES_TAGS", inverseName = "AUTO_TAGS_FILES")
#JoinTable(name = "AUTOTAGS_FILES", joinColumns = #JoinColumn(name = "FILES_ID"), inverseJoinColumns = #JoinColumn(name = "TAGS_ID"))
public Set<Tags> getAutoTags() {
return autoTags;
}
// ...
}
With these changes, the following test passes (the session is created outside the test method):
#Test
public void removeFiles() {
Files files = (Files) session.get(Files.class, 1L);
session.delete(files);
session.flush();
Query q = session.createQuery("from Files f where f.id = :id");
q.setParameter("id", 1l);
Files result = (Files) q.uniqueResult();
assertNull(result);
}
And generates the following queries:
Hibernate: select files0_.id as id100_0_ from Files files0_ where files0_.id=?
Hibernate: delete from AUTOTAGS_FILES where FILES_ID=?
Hibernate: delete from Files_TAGS where userFiles_id=?
Hibernate: delete from Files where id=?
Hibernate: select files0_.id as id100_ from Files files0_ where files0_.id=?
Tested with Derby.

Categories

Resources