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>();
I'm working on the following code:
Product
#Entity
#Table(name = "products",
public class Product extends RepresentativeModel {
#ManyToMany(mappedBy = "products", targetEntity = Category.class)
private Set<Category> categories = new HashSet<>();
}
Category
#Entity
#Table(name = "categories",
public class Category extends RepresentativeModel {
#ManyToMany
#JoinTable(name = "productToCategory",
joinColumns = {#JoinColumn(name = "categoryId")},
inverseJoinColumns = {#JoinColumn(name = "productId")})
private Set<Product> products = new HashSet<>();
}
EM config
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Qualifier("dataSource") DataSource source) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(source);
entityManagerFactory.setPackagesToScan("com.x.model");
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
log.debug("PERSISTENCE UNIT " + PERSISTENCE_UNIT_NAME);
return entityManagerFactory;
}
Package info
package is com.x.model for both
Properties properties = new Properties();
properties.put("hibernate.default_schema", "public");
entityManagerFactory.setJpaProperties(properties);
Problem
Products tables exists in the database, but I'm getting this error message:
org.postgresql.util.PSQLException: ERROR: relation "products" does not
exist
I tried to change entity name to "products" or to change mappedBy to "Product", but without success.
How to fix this?
I've had the same problem, and all the answers in internet did not help me. And I find out that postrgres creates tables in case sensitive manner, if you add double quotes when creating tables.
"auteur" and auteur are two different names.
I don’t know exactly how it works, but apparently Hibernate cannot find the #JoinTable for the #ManyToMany relation, because, as I wrote above, this is a different table for postgres. Knowing this solved my problem. Maybe this will help someone else.
I have 2 entities - User and Role with mapping #ManyToOne. I want to change role in user, but Role want's to be updated too.
User entity:
#ManyToOne
#JoinColumn(name = "role_id", insertable = false, updatable = false)
private Role role;
role entity:
#OneToMany(mappedBy = "role")
private Set<User> users;
The error I get is:
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance beforeQuery flushing: com.spring.model.Role
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:144)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1434)
My tables in DB are not set with CASCADE UPDATE or INSERT. I wasn't able to find appropriate solution. Thanks for your help
EDIT:
This is how I update User
public void update(User user) {
User entity = dao.findById(user.getId());
if(entity!=null) {
entity.setRole(user.getRole());
}
}
EDIT2:
My hibernate configuration
#Configuration
#EnableTransactionManagement
#ComponentScan({ "com.spring.configuration" })
#PropertySource(value = { "classpath:application.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.spring.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
You should use the cascade attribute of the OneToMany annotation, it does not have any relationship with the database cascade operations (but can be affected by that).
#OneToMany(cascade = CascadeType.ALL, mappedBy="role", orphanRemoval = true)
private Set<User> users;
This way the operation will be propagated to the collection elements. You can check this answer for more info.
You can also modify the cascade property of the ManyToOne annotation.
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "role_id")
private Role role;
I'm using the mentioned libraries and want to persist a simple object, that has a one-to-one-relationship with another, without persisting the associated object. That might sound simple, because it should be the default behaviour. However, in my case for some reason it seems not to be.
I'm using an entityManager configured by this code:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setDataSource(dataSource);
return factory;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
Here are my entity classes:
#Entity
#Table(name = "`Table_A`", schema = "public")
public class ClassA {
#Id
#Column(name = "a_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "value")
private String value;
#Column(name = "time")
private LocalDateTime time;
#OneToOne(targetEntity = ClassB.class, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "b_id_ref")
private ClassB b;
// getters and setters
}
#Entity
#Table(name = "`Table_B`", schema = "public")
public class ClassB {
#Id
#Column(name = "b_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "value")
private String value;
// getters and setters
}
I don't want the object that b in ClassA references to be persisted. But if I change value in an instance of ClassA and also value in the corresponding instance of ClassB. I wrote a small test to demonstrate the program flow:
#Autowired
private ClassARepository repository;
#PersistenceContext
private EntityManager em;
#Test
public void testCascading() {
Session hibernateSession = em.unwrap(Session.class);
String newValueA = "Sissi";
String newValueB = "Franzl";
ClassA a = createTestInstance();
ClassB b = a.getB();
String oldValueA = token.getTokenValue();
String oldValueB = user.getFirstname();
a.setValue(newValueA);
b.setValue(newValueB);
repository.save(a);
em.flush();
hibernateSession.evict(a);
hibernateSession.evict(b);
ClassA loadedA = repository.findById(1L);
assertThat(loadedA.getB().getValue(), equalTo(oldValueB));
assertThat(loadedA.getValue(), equalTo(newValueA));
}
I checked that the bahaviour is the same in the "real world". Like it is stated here the save-operation should not be cascaded. I must miss something here. Is there maybe a global setting that changes the default behaviour? What can I do to achieve the desired behaviour?
Your test is transactional. So you're getting a managed B, and changing the value of one of its field. And the change is thus made persistent.
Your calls to save() and flush() are useless, by the way. The whole point of JPA is that when entities are managed, their state is made persistent automatically. It's a feature, not a bug.
There are two entities: parent and child one.
#Entity
#Table(name = "university_group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
#OneToMany(mappedBy = "group", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Student> students = new HashSet<>();
// getters, setters, constructor, equals+hashcode ...
}
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
private String password;
private String email;
#ManyToOne(optional = false)
private Group group;
// getters, setters, constructor, equals+hashcode ...
}
After removing group by em.remove(group) an exception is thrown:
javax.persistence.PersistenceException:
org.hibernate.exception.ConstraintViolationException ...
org.hibernate.engine.jdbc.spi.SqlExceptionHelper: ERROR:
UPDATE or DELETE in table "university_group" breaks foreign key constraint "fk_20su8ubuwt33je1a3ygal7wd6" of table "student"
It seems like hibernate is not deleting students before the group by means of Persistence Provider although it should. Of course, I am able to enable DB cascading, but I would better solve the problem.
Any ideas?
Configured the EntityManager by Spring configs
#Configuration
#EnableTransactionManagement
#PropertySource({"classpath:db.properties"})
public class PersistenceContext {
private static final String BASE_MODEL_SCAN_PACKAGE = "com.chiefhelpsystem.model";
#Value("${db.driverClassName}")
private String dbClassName;
#Value("${db.url}")
private String dbUrl;
#Value("${db.username}")
private String dbUserName;
#Value("${db.password}")
private String dbPassword;
#Bean
DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setMaxIdle(20);
ds.setMinIdle(0);
ds.setMaxActive(20);
ds.setDriverClassName(dbClassName);
ds.setUrl(dbUrl);
ds.setUsername(dbUserName);
ds.setPassword(dbPassword);
return ds;
}
#Bean
PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
#Bean(destroyMethod = "destroy")
LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emFactory =
new LocalContainerEntityManagerFactoryBean();
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setShowSql(true);
emFactory.setDataSource(dataSource());
emFactory.setPackagesToScan(BASE_MODEL_SCAN_PACKAGE);
emFactory.setJpaVendorAdapter(jpaVendorAdapter);
emFactory.setJpaProperties(jpaProps());
emFactory.setPersistenceProvider(new HibernatePersistenceProvider());
return emFactory;
}
private Properties jpaProps() {
Properties properties = new Properties();
properties.setProperty("format_sql", "true");
return properties;
}
}
Hibernate 4.3.11, Spring 4.3.2
The problem was in the incorrect hachcode() method realization. As soon as I deleted it from sources, the "un-managed deleting" problem has appeared in hibernate TRACE logs and it should be further fixed.