I'm upgrading Hibernate4 to Hibernate5 (Spring 4.3.7) (Spring-data-jpa 1.11.1) and facing the problem with saving of bidirectionally associated entities save using JpaRepository.save(ownerObject).
Here are my entities :-
DataType (Owner Entity) :-
#Entity
#Table(name = "DATATYPE")
public class DataType {
private List<DataFormat> mFormats;
public DataType() {
mFormats = new ArrayList<DataFormat>();
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "dataType")
public List<DataFormat> getFormats() {
return mFormats;
}
public void setFormats(List<DataFormat> formats) {
mFormats = formats;
}
}
DataFormat Entity (Inverse side):-
#Entity
#Table(name = "DATAFORMAT")
public class DataFormat {
private DataType mDataType;
public DataFormat() {
}
#ManyToOne
#ForeignKey(name = "DATATYPE_FK")
#JoinColumn(name = "DATATYPE_ID")
public DataType getDataType() {
return mDataType;
}
public void setDataType(DataType dataType) {
mDataType = dataType;
}
}
Save/Persist the entities :-
DataType dataType = dataTypeRepository.findOne("DATE_TIME");
if (dataType == null) {
dataType = new DataType();
}
final DataFormat customFormat = new DataFormat();
dataType.setDefaultFormat(customFormat);
dataTypeRepository.save(dataType);
This works perfect with Hibernate4, that first saves the DataType and then DataFormat.
But with Hibernate5, it tries to save the DataFormat (inverse side) first, and then result into error in my case since my DB has constraints for DataFormat to have owner reference.
Although it works if I save the entities like this at the place of dataTypeRepository.save(dataType); :-
entityManager.persist(dataFormat);
entityManager.persist(dataType);
What could be the problem here.
Related
I have this query
DELETE
FROM bookings as b
WHERE b.check_out = CURRENT_DATE;
and I get
Cannot delete or update a parent row: a foreign key constraint fails (online_booking_app.booked_rooms, CONSTRAINT FK3x1lpikb2vk75nx41lxhdicvn FOREIGN KEY (booking_id) REFERENCES bookings (id))
My Booking entity has CascadeType.ALL and mapped by matches the other side - from my research these are some of the mistakes that could lead to this message.
Here is the BookingEntity:
#Entity
#Table(name = "bookings")
public class BookingEntity extends BaseEntity {
#OneToMany(mappedBy = "booking",cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookedRoomsEntity> bookedRooms = new ArrayList<>();
private String firstName;
private String lastName;
public List<BookedRoomsEntity> getBookedRooms() {
return bookedRooms;
}
public BookingEntity setBookedRooms(List<BookedRoomsEntity> bookedRooms) {
this.bookedRooms = bookedRooms;
return this;
}
BookedRoomsEntity
#Entity
#Table(name = "booked_rooms")
public class BookedRoomsEntity extends BaseEntity {
#ManyToOne()
private BookingEntity booking;
public BookingEntity getBooking() {
return booking;
}
public BookedRoomsEntity setBooking(BookingEntity booking) {
this.booking = booking;
return this;
}
The CascadeType does only apply to EntityManager operations.
You therefore have two options:
Load the entities to be deleted first and then use EntityManager.remove
Remove the referencing entities first with a separate JPQL statement.
On my MySql project I got this particular model with 3 entities: Prodotto with many childs QuotaIngrediente, that in turn is Many-to-One child of Ingrediente too. All my relationships are bi-directional.
All of them got an autogenerated integer Id and other fields removed to focus on the interesting ones.
#Entity
public class Prodotto {
private List<QuotaIngrediente> listaQuoteIng = new ArrayList<QuotaIngrediente>();
#OneToMany(mappedBy = "prodotto", cascade = CascadeType.ALL, orphanRemoval = true)
public List<QuotaIngrediente> getListaQuoteIng() {
return listaQuoteIng;
}
#Entity
public class QuotaIngrediente{
private Prodotto prodotto;
private Ingrediente ing;
private Double perc_ing;
#ManyToOne
#JoinColumn(name = "prodotto")
public Prodotto getProdotto() {
return prodotto;
}
#ManyToOne
#JoinColumn(name = "ing")
public Ingrediente getIng() {
return ing;
}
#Entity
public class Ingrediente {
private Set<QuotaIngrediente> quoteIng = new HashSet<QuotaIngrediente>();
#OneToMany(mappedBy = "ing", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<QuotaIngrediente> getQuoteIng() {
return quoteIng;
}
I'm using SpringData Specification and I can build a query to get Prodotto based on Ingrediente criteria, this way:
public static Specification<Prodotto> getProdottoByIngSpec (String ing) {
if (ing != null) {
return (root, query, criteriaBuilder) -> {
query.groupBy(root.get(Prodotto_.id));
return criteriaBuilder.like(((root.join(Prodotto_.listaQuoteIng))
.join(QuotaIngrediente_.ing))
.get(Ingrediente_.nome), "%"+ing+"%");
};
It works as expected, but now I want to sort it by the QuotaIngrediente perc_ing field OF THAT SPECIFIC INGREDIENTE.
Obviously I'm asking how to do it on DB, not in business logic.
I was struggling with a false problem due to a wrong assumption of mine. Solution was the simplest. Just sort by orderBy CriteriaQuery method. The query I used to search already filtered the QuotaIngrediente returning just the lines that match my search criteria. Then this is the only line I had to add to my Specification:
query.orderBy(builder.desc((root.join(Prodotto_.listaQuoteIng))
.get(QuotaIngrediente_.perc_ing)));
I have Employee and Functions, Bank classes
Employee and Function have #OneToMany relationship and
Employee and Bank have also #OneToMany relationship.
if the user edits the form and change the function and/or bank
I want to update the relationship. but when I change the relationship
I get Duplicate entry exception due to the uniqueness of a column because
the Employee object persisted as a new entity
I tried to remove the employee from the function and set the employee's function to null and get a new function and add the employee to it
and set the new function but it doesn't work. any idea, please
#Entity
public class Employee extends GeneratedIdEntity<Long> {
#ManyToOne(optional = false)
private Functions function;
#ManyToOne(optional = false)
private Bank bank;
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<RubricValue> rubricsValues = new ArrayList<>();
#OneToMany(
mappedBy = "employee",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
List<EmployeeStatus> employeesStatus=new ArrayList<>();
}
#Entity
public class Functions extends GeneratedIdEntity<Long>{
#OneToMany(
mappedBy = "function",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees=new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Entity
public class Bank extends GeneratedIdEntity<Long> {
#OneToMany(
mappedBy = "bamk",
fetch = LAZY,
cascade = ALL,
orphanRemoval = true
)
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee employee ){
employees.add(employee);
}
public void removeEmployee(Employee employee){
employees.remove(employee);
}
}
#Stateless
public class EmployeeService extends BaseEntityService<Long, Employee> {
#Inject
FunctionService functionService;
#Inject
BankService bankService;
public void update(Employee employee, String newFunctionName, String newBankName) {
if (!employee.getBank().getName().equals(newBankName)) {
employee.getBank().removeEmployee(employee);
employee.setBank(null);
Bank newBank = bankService.getByName(newBankName);
newBank.addEmployee(employee);
employee.setBank(newBank);
}
if (!employee.getFunction().getName().equals(newFunctionName)) {
employee.getFunction().removeEmployee(employee);
employee.setFunction(null);
Functions newFunction = functionService.getByName(newFunctionName);
newFunction.addEmployee(employee);
employee.setFunction(newFunction);
}
}
}
the exception stack trace Caused by:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
'dkfhks32' for key 'REGISTRATIONNUMBER' at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115)
at
com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)
at
com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:960)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1116)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1066)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1396)
at
com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1051)
at
com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:127)
at sun.reflect.GeneratedMethodAccessor54.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
com.sun.gjc.spi.jdbc40.ProfiledConnectionWrapper40$1.invoke(ProfiledConnectionWrapper40.java:437)
at com.sun.proxy.$Proxy268.executeUpdate(Unknown Source) at
org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:898)
You don't have to change the bank for an employee by removing the reference of existing bank first. You can simply go:
if (!employee.getBank().getName().equals(newBankName)) {
Bank newBank = bankService.getByName(newBankName);
//You must also do an entity validation/null check here. The newBank might not be present after all.
employee.setBank(newBank);
}
This will update the mappings correctly. Same goes for updating function of an employee
I have an issue, what I am close to solution or better said I come closer how to avoid the issue EntityExistsException: A different object with the same identifier value BUT without understanding what is really the problem. To keep code short and concentrated I did a small application to simulate the problem in my project.
First I have a CrudRepository what stays always the same:
public interface EntityARepository extends CrudRepository<EntityA, Long> {}
First I have to entities, one of them has a relation to the other:
#Entity
#EqualsAndHashCode(of={"name"})
#ToString(of={"name"})
#XmlRootElement
public class EntityA {
#Id
#GeneratedValue
#Setter
private Long id;
#Setter
#Column(nullable=false, unique=true)
private String name;
#Setter
#ManyToOne(fetch=EAGER, cascade={PERSIST, MERGE})
private EntityB entityB;
}
#ToString(of = { "name" })
#EqualsAndHashCode(of = { "name" })
#Entity
class EntityB {
#Id
// #GeneratedValue => produces issue!
#Setter
private Long id;
#Setter
#XmlAttribute
#Column(nullable=false, unique=true)
private String name;
}
Then I generate data and try to save them:
#Component
public class DatabaseInitializer implements InitializingBean {
#Autowired EntityARepository repository;
#Override
public void afterPropertiesSet() throws Exception {
final Set<EntityA> aEntities = createAEntities();
repository.save(aEntities);
}
private Set<EntityA> createAEntities() throws Exception {
Set<EntityA> aEntities = new HashSet<>();
aEntities.add(getFirstEntityA());
aEntities.add(getSecondEntityA());
return aEntities;
}
private EntityA getFirstEntityA(){
EntityA a = new EntityA();
// a.setId(1L);
a.setName("a-1");
a.setEntityB(getFirstEntityB());
return a;
}
private EntityA getSecondEntityA(){
EntityA a = new EntityA();
// a.setId(2L);
a.setName("a-2");
a.setEntityB(getFirstEntityB());
return a;
}
private EntityB getFirstEntityB() {
EntityB b = new EntityB();
b.setId(1l);
b.setName("b-1");
return b;
}
}
With this constallation I become a EntityExistsException:
Caused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.example.EntityB#1]
When firstEntityA gets saved, it also saves firstEntityB (because of cascade). So after that Session contains already entity of class EntityB with id 1.
When secondEntityA gets persisted, it also calls save on firstEntityB and then exception gets thrown.
The reason is that you can't call save on an object with id that is already present in the Session.
There are many ways to fix it. For example you can call merge instead of save.
repository.merge(aEntities);
Another way to fix this would to make both instances of EntityA have a referene to the same EntityB object:
private Set<EntityA> createAEntities() throws Exception {
Set<EntityA> aEntities = new HashSet<>();
EntityB entityB = getFirstEntityB();
aEntities.add(getFirstEntityA(entityB));
aEntities.add(getSecondEntityA(entityB));
return aEntities;
}
private EntityA getFirstEntityA(EntityB entityB){
EntityA a = new EntityA();
// a.setId(1L);
a.setName("a-1");
a.setEntityB(entityB);
return a;
}
private EntityA getSecondEntityA(EntityB entityB){
EntityA a = new EntityA();
// a.setId(2L);
a.setName("a-2");
a.setEntityB(entityB);
return a;
}
private EntityB getFirstEntityB() {
EntityB b = new EntityB();
b.setId(1l);
b.setName("b-1");
return b;
}
I am making an application based on JPA/EclipseLink, I implemented a soft delete functionality using #AdditionalCriteria in the root class of the hierarchy of entities (all entities inherit from this).
My problem is that now, I need to create a special entity that contains multiple relationships with other entities; and I need recover all relationed entities, including soft deleted ones. It is possible disabled #AdditionalCriteria only in the relations of this special entity with EclipseLink? If not, what is the best option to do this? My code looks like the following:
////Example of my top entity class (all others inherit from this)
#AdditionalCriteria("this.deleted = false")
public abstract class TopHierarchyClass {
···
#Column(name = "deleted")
protected boolean deleted = false;
···
}
//Example of entity that needs recover all relationed entities including soft deleted
#Entity
#Table(name = "special_entities")
public class SpecialEntity extends EntityBase {
···
#JoinColumn(name = "iditem", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Item item;
···
}
#Entity
#Table(name = "items")
public class Item extends EntityBase {
···
}
Thanks in advance
Create a new entity for the same table without the #AdditionalCriteria. This way you can retrieve all records from that table without applying the additional filter.
For example:
public interface Person {
Long getId();
void setId(Long id);
Date getDeletedAt();
void setDeletedAt(Date deletedAt);
}
#MappedSupperclass
public class PersonEntity implements Person {
#Id
private Long id;
private Date deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Date getDeletedAt() { return deletedAt; }
public void setDeletedAt(Date deletedAt) { this.deletedAt = deletedAt; }
}
#Entity
#Table(name = "people")
#AdditionalCriteria("this.deletedAt is null")
public class ActivePersonEntity extends PersonEntity {
}
#Entity
#Table(name = "people")
public class RawPersonEntity extends PersonEntity {
}
public class PeopleRepository {
#PersistenceContext
private EntityManager em;
public List<Person> all() {
return all(false);
}
public List<Person> all(boolean includeSoftDeleted) {
if (!includeSoftDeleted) {
return em.createQuery("select p from ActivePersonEntity p", ActivePersonEntity.class).getResultList();
} else {
return em.createQuery("select p from RawPersonEntity p", RawPersonEntity.class).getResultList();
}
}
}
Also, if your #AdditionalCriteria is in a super class, you may override it by declaring a new empty #AdditionalCriteria in a sub class:
You can define additional criteria on entities or mapped superclass.
When specified at the mapped superclass level, the additional criteria
definition applies to all inheriting entities, unless those entities
define their own additional criteria, in which case those defined for
the mapped superclass are ignored.
#AdditionalCriteria doc