Cannot persist Entity with Shared ID - java

I am practicing JavaEE technologies. Now I am focusing in JPA with Hibernate. The project has the following entities:
Book:
#Entity #Table(name = "book")
public class Book extends BaseEntity<Long> {
#Id
private Long id;
#NotNull
private String name;
#OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetails details;
//getters/setters
}
BookDetails:
#Entity
#Table(name = "book_details")
public class BookDetails extends BaseEntity<Long> {
#Id
private Long id;
#MapsId
#OneToOne
private Book book;
#NotNull
#Column(name = "number_of_pages")
private int numberOfPages;
//getters/setters
}
Now, the respective EJB Service classes:
BookService:
#Stateless
public class BookService extends BaseEntityService<Long, Book> {
public void createBook(Book book) {
super.getEntityManager().persist(book);
}
//update, delete, find and findAll methods
}
BookDetailsService:
#Stateless
public class BookDetailsService extends BaseEntityService<Long, BookDetails> {
public void createBookDetails(BookDetails details) {
super.getEntityManager().persist(details);
//super.persist(details); //This method would not work to persisting entity with shared Id, because it verifies if ID is null
}
//update, delete and find methods
}
The problem:
When I try to persist a new book along with its details as follows:
Book b = new Book();
b.setId(123L);
b.setName("Bible");
bookService.createBook(b);
//The entity Book is correctly persisted in the DB.
BookDetails d = new BookDetails();
d.setNumberOfPages(999);
d.setBook(b);
//b.setDetails(d); //don't do this
bookDetailsService.createBookDetails(d);
//BookDetails is not persisted in the DB, throws exception....
Throws the following exception:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '123' for key 'PRIMARY'
The Book entity is persisted but not the BookDetails.
I followed this tutorials:
Hibernate Tips: How to Share the Primary Key in a One-to-One Association
The best way to map a #OneToOne relationship with JPA and Hibernate
Aditional Information:
JDK 1.8
Payara 5
MySQL 8.0
Hibernate 5.3.4
OmniPersistence library. (Utilities for JPA, JDBC and DataSources)
You can look at the project repository here.

I could already solve the problem and it was due to the way the entities persisted and the transactionality of the methods in a JavaEE application.
When calling a business method of an EJB in which classes are persisted, upon completion of this method the transaction ends, therefore the persistence context is closed and the attached classes (managed) become detached (unmanaged). See this.
So after persisting Book b:
bookService.createBook (b);
That transaction ends and that is why the book is persisted and, in addition, it is no longer managed. So when I link the book to the details:
d.setBook (b);
That is something that must be done to persist an entity that shares Id, the parent entity (in this case b) has to be managed.
There are two solutions that i could find:
Solución 1:
Attach book b to the persistence context of the details creation transaction. See this.
b = getEntityManager.merge(b);
Solución 2: (The one that I chose)
Make the call of the BookService business method within the business method of the BookDetailsService, which implies transferring the dependency injection. Thus, a single transaction is made by persisting the two entities.
//BookDetailsService class
#Inject
BookService bookService;
public void createBookDetails(BookDetails details) {
Book b = new Book();
b.setId(details.getId());
b.setName("Bible");
bookService.createBook(b);
details.setBook(b);//b still managed
super.persist(details);
}
I think this solution is more cleaner and coherent, because BookDetailsService will need always the services from BookService.

#OneToOne on BookDetails property of Book class indicates that there is a one-to-one association from Book to BookDetails. Also, note the use of cascade = CascadeType.ALL.Since BookDetails entity cannot exist without Book entity, with this cascade setting, BookDetails entity will be updated/deleted on subsequent update/delete on Book entity.

You have to add cascadetype on book details:
#OneToOne(mappedBy = "book", cascade = CascadeType.ALL)
private BookDetails details;
Refer here for more details
CascadeType.ALL will do all the operations such as save, delete and merge when book entity is saved.
Now as we have added CascadeType.ALL in Book entity you have to set bookdetails object in book. See below:
Book b = new Book();
BookDetails d = new BookDetails();
b.setBookDetails(d);
b.setId(123L);
b.setName("Bible");
d.setId(111L); //random id that is not existing if d is new record
d.setNumberOfPages(999);
bookService.createBook(b);
Saving book entity will save book as well as book details entity. You may also try hibernate.show_sql=true to see what all queries are executed when you save book entity.

Related

Why #OneToMany relations generate additional SQL select queries?

#Entity
public class Author {
#OneToMany(cascade = {PERSISTE, MERGE},
mappedBy = "author")
private List<Book> books = new ArrayList<>();
public void addBook(Book book) {
this.books.add(book);
book.setAuthor(this);
}
//other fields
}
#Entity
public class Book{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
}
public class BookService {
AuthorRepository repository;
#Transactional
public void saveBooks(){
Book newBook = new Book);
Author author = repository.findByid(1L);
author.addBook(newBook);
}
}
In a database, an author already contains one book.
I create a new book for an author and want to save it.
But when the function addBook is called the existing book is loaded from the database. I see the additional SQL query in logs:
#Query["select book ...."]
How can I fix it?
I don't want to have additional SQL selects
This is just how PersistentBag works - Hibernate loads it when you perform action on list. I would simply replace adding new Book to Author's books list with referencing created Book to existing Author
public class BookService {
BookRepository bookRepository;
AuthorRepository authorRepository;
#Transactional
public void saveBooks() {
Book newBook = new Book();
Author author = authorRepository.findByid(1L);
newBook.setAuthor(author);
bookRepository.save(newBook);
}
}
The reason for this is the basic premise under which JPA works: You load an object graph, you manipulate it, at the end of the transaction JPA makes sure the changes end up in the database.
I see two ways to avoid the select.
reverse the relationship: When the Book references the Author there is no need to load the collection, since it doesn't even exist in the first place. If you actually need the collection you can always use a dedicated query for that.
Drop back to SQL and just execute a SQL insert statement. Of course this can lead to inconsistencies between your database and the 1st level cache of JPA. Make sure you understand how the 1st level cache works and what it is used for.

How to add existing value in many to many relationship spring boot [duplicate]

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.
Here's a snippet of the code:
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
How can I correctly save a Transaction, associated with an already persisted Account object?
The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.
I have had the same problem and CascadeType.MERGE has worked for me.
I hope you are sorted.
This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.
As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.
An example setter for the Many side is in this link.
After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.
Remove cascading from the child entity Transaction, it should be just:
#Entity class Transaction {
#ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER can be removed as well as it's the default for #ManyToOne)
That's all!
Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.
But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).
Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.
You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...
Guess what happens if you call remove(transaction) still having "cascade ALL" in that #ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?
Don't pass id(pk) to persist method or try save() method instead of persist().
Removing child association cascading
So, you need to remove the #CascadeType.ALL from the #ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.
#ManyToOne(fetch= FetchType.LAZY)
Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.
Setting both sides of the association
Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.
You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.
Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.
My Spring Data JPA-based answer: I simply added a #Transactional annotation to my outer method.
Why it works
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
Reference:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
An old question, but came across the same issue recently . Sharing my experience here.
Entity
#Data
#Entity
#Table(name = "COURSE")
public class Course {
#Id
#GeneratedValue
private Long id;
}
Saving the entity (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Fix
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Conclusion : If the entity class has #GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)
If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).
In example of Account -> Transactions:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.
In your entity definition, you're not specifying the #JoinColumn for the Account joined to a Transaction. You'll want something like this:
#Entity
public class Transaction {
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
#JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
EDIT: Well, I guess that would be useful if you were using the #Table annotation on your class. Heh. :)
Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.
Alternatively, but effectively the same, this annotation declaration results in the same exception:
#Entity
public class Transaction {
#Id
#org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
#GeneratedValue(generator="system-uuid")
private Long id;
....
So, don't set the id value in your application code when it's already being managed.
Here is my fix.
Below is my Entity. Mark that the id is annotated with #GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate.
Mind you if the entity id field is not marked with #GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()
#Entity
#Data
#NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String key;
#Column
private String value;
}
And here is my main class.
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
When I tried saving that, it was throwing that
PersistentObjectException: detached entity passed to persist.
All I needed to fix was remove that id setting line for the simpleObject in the main method.
Maybe It is OpenJPA's bug, When rollback it reset the #Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the #Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* #param entity
* #param detached
* #return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
You need to set Transaction for every Account.
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
Or it colud be enough (if appropriate) to set ids to null on many side.
// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
foreach(Account account : accounts){
account.setId(null);
}
transactionObj.setAccounts(accounts);
// just persist transactionObj using EntityManager merge() method.
cascadeType.MERGE,fetch= FetchType.LAZY
Resolved by saving dependent object before the next.
This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation #ManytoOne
#OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
worked for me.
In my case I was committing transaction when persist method was used.
On changing persist to save method , it got resolved.
If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key)
Then this will work.
Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.
Add a #Version annotation to all mapped entities
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "orders")
private CustomerOrders orders;
}
#Entity
public class CustomerOrders {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
private BigDecimal value;
}
This error comes from the JPA Lifecycle.
To solve, no need to use specific decorator. Just join the entity using merge like that :
entityManager.merge(transaction);
And don't forget to correctly set up your getter and setter so your both side are sync.
So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.
But in my case I was trying to set a Value to a Field which was annotated with #Id.
So if you are using #Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.
The problem here is lack of control.
When we use the CrudRepository/JPARepository save method we loose the transactional control.
To overcome this issue we have Transaction Management
I prefer the #Transactional mechanism
imports
import javax.transaction.Transactional;
Entire Source Code:
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Data
#ToString(exclude = {"employee"})
#EqualsAndHashCode(exclude = {"employee"})
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
#SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
#Setter(AccessLevel.NONE)
#Column(name = "project_id",updatable = false,nullable = false)
private Integer pId;
#Column(name="project_name",nullable = false,updatable = true)
private String projectName;
#Column(name="team_size",nullable = true,updatable = true)
private Integer teamSize;
#Column(name="start_date")
private Date startDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projectemp_join_table",
joinColumns = {#JoinColumn(name = "project_id")},
inverseJoinColumns = {#JoinColumn(name="emp_id")}
)
private List<Employee> employees;
}
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#EqualsAndHashCode(exclude = {"projects"})
#ToString(exclude = {"projects"})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
#SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
#Setter(AccessLevel.NONE)
#Column(name = "emp_id",nullable = false,updatable = false)
private Integer eId;
#Column(name="fist_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#ManyToMany(mappedBy = "employees")
private List<Project> projects;
}
package com.oracle.repo;
import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
package com.oracle.repo;
import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRepo extends JpaRepository<Project,Integer> {
}
package com.oracle.services;
import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
#Component
public class DBServices {
#Autowired
private ProjectRepo repo;
#Transactional
public void performActivity(){
Project p1 = new Project();
p1.setProjectName("Bank 2");
p1.setTeamSize(20);
p1.setStartDate(new Date(2020, 12, 22));
Project p2 = new Project();
p2.setProjectName("Bank 1");
p2.setTeamSize(21);
p2.setStartDate(new Date(2020, 12, 22));
Project p3 = new Project();
p3.setProjectName("Customs");
p3.setTeamSize(11);
p3.setStartDate(new Date(2010, 11, 20));
Employee e1 = new Employee();
e1.setFirstName("Pratik");
e1.setLastName("Gaurav");
Employee e2 = new Employee();
e2.setFirstName("Ankita");
e2.setLastName("Noopur");
Employee e3 = new Employee();
e3.setFirstName("Rudra");
e3.setLastName("Narayan");
List<Employee> empList1 = new LinkedList<Employee>();
empList1.add(e2);
empList1.add(e3);
List<Employee> empList2 = new LinkedList<Employee>();
empList2.add(e1);
empList2.add(e2);
List<Project> pl1=new LinkedList<Project>();
pl1.add(p1);
pl1.add(p2);
List<Project> pl2=new LinkedList<Project>();
pl2.add(p2);pl2.add(p3);
p1.setEmployees(empList1);
p2.setEmployees(empList2);
e1.setProjects(pl1);
e2.setProjects(pl2);
repo.save(p1);
repo.save(p2);
repo.save(p3);
}
}

why is detached entity treated as transient?

I am using Hibernate as JPA provider. I have fetched account entity from the database and detached it. So, the account entity must now be in a detached state.
Now when I try to persist the detached entity, I was expecting it to fail, as entitymananger.persist method works only on transient and managed entities. But it is running INSERT query on database.
Further debugging the entitymananger.persist implementation, I found that it is considering account entity as TRANSIENT. I put the breakpoint here to see entity state,
The code used for testing:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MainApplication.class)
public class HibernateTest {
#PersistenceContext
private EntityManager entityManager;
#Test
#Transactional
#Commit
public void testDetached() {
Account account = entityManager.find(Account.class, 1); // account is now managed entity
entityManager.detach(account); // detach the account entity
entityManager.persist(account);// entityManager is treating account as transient object and ran INSERT query on the database
}
}
Adding the Account class
#Entity
#lombok.Data
#Table(name = "account")
class Account {
#Id
#Column(name = "userid")
private int userid;
#Column(name = "balance")
private int balance;
}
My questions are:
Why is entitymanager.persist considering detached entity as transient ?
I tried understanding Hibernate determination of whether an entity is transient or detached (code). the logic is complex and couldn't internalize why it is done the way it is done.
Please let me know if you need any further details. Thanks.
An entity is considered DETACHED if :
1. ID is not null (otherwise it is transient)
2. It is not managed
3. It has not been removed
Im not sure how you are assigning the ID property, but i guess the problem is somewhere around there.
Leave the ID generation to hibernate:
#Id
#GeneratedValue <<<<<<<
#Column(name = "userid")
private int userid;
That should fail the test.

CascadeType doesn't work in in-memory database

I have a webapp with database where two Entities have many-to-many relationship but I implemented join table manually. When one of the entities gets deleted it deletes all entries in the join table and updates the other entity so all works perfectly fine, but now I'm supposed to write a test for this feature. For tests I am using in-memory database and that's really the only difference, the same methods with the same annotations (and cascade types) are called but I keep getting:
org.hibernate.exception.ConstraintViolationException: integrity constraint violation: foreign key no action; FKC17477FD8940DF2B table ENTITY1_ENTITY2
I didn't paste any code as I don't believe there is anything wrong with it since it's working. I don't ask to fix this for me, I just need to know what is likely to cause this kind of behavior because I've just ran out of ideas and I don't know what else to search for... Thanks
EDIT: here's some code:
#Entity
#Table(name = "interviewer")
public class Interviewer implements Identifiable {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "interviewer_id")
private Collection<InterviewerTechnology> technologies;
}
#Entity
#Table(name = "technology")
public class Technology implements Identifiable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "technology_id")
private Collection<InterviewerTechnology> technologies;
}
#Entity
#Table(name = "interviewer_technology")
public class InterviewerTechnology implements Identifiable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private Interviewer interviewer;
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
private Technology technology;
}
#Component
public class TechnologyDao extends AbstractEntityDao<Technology> {
public void remove(Integer id) {
Technology technology = find(id);
em.remove(technology);
}
}
This code does exactly what I want it to do, it just seems like database used for tests does not see CascadeType parameters that do all the job here
I have found the problem and it was #Transactional annotation. All my test DAOs were extending generic test DAO which was annotated with #Transactional and I blindly annotated every single DAO with it again. The problem here is that some operations need to be performed as single transactions (may need flush() after being executed) so that data is available for other operations straight away. Consider following example:
#Transactional
public abstract class AbstractEntityDao<E> {
#PersistenceContext(unitName = "some-persistence")
protected EntityManager em;
public E create(E e) {
em.persist(e);
return e;
}
(...)
}
which means that every method in this class is a transaction. Now if we annotate another class that extends this class with #Transactional every method will be another transaction, which means if we delete several things in one method it should take several transactions (they all need flush() method to be called in order to execute cascade) but instead they will run as one transaction (unless we specify Propagation). Let this be a lesson for everyone (especially me) to think carefully about which operations need separate transactions and which can be executed as one.

JPA OneToOne and OneToMany entity instantiation/creation

My application uses a lot of OneToMany and OneToOne references between domain level value-objects, most of them are entities, either being a super class or a subclass of something.I would like to provide my application a consistent(yet easy) way to save those instances and the actual method save() is as such
#Transactional
public void save(Post post){
try{
JPA.em().persist(post);
}catch (EntityExistsException eee){
JPA.em().merge(post);
}catch(ConstraintViolationException cve){
JPA.em().refresh(post);
}
}
The current problem is how to properly instantiate those object and which strategy choose in the cascadeType, i would like to save nested object when saving an object with references with other entities, it works now but only for the first time, after that i get a Unique index or primary key violation given that SQL insert into Utente (passwd, DTYPE, username) values (?, 'Redattore', ?) [23505-168].Clearly my JPA provider (hibernate 3.6.9) fails to not update an existing row, instead it tries to insert a new entry in the DB.Here are some classes i am using:
#Entity
#Table
public class Post extends Domanda {
#Column(nullable = false)
private String nome;
#OneToMany(cascade = CascadeType.ALL)
private List<Commento> commenti;
#OneToMany(cascade = CascadeType.ALL)
private List<Risorsa> risorse;
#OneToOne(cascade = CascadeType.ALL)
private NodoApprendimento nodo;
#Column
private int visibilità;
#Column
private boolean isDraft;
Post is referenced by a few classes among which i there is:
#Entity
public abstract class Partecipante extends Utente{
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
private Set<Post> contributi;
Then i would like to know the proper way to initialize and persisteORupdate those referenced object in the database, thanks in advance.
This isn't a provider problem, but a usage issue. When you call persist, JPA does not require providers to execute an insert immediately - they are usually delayed to flush or commit time. So you will not get an EntityExistsException in most cases. Either way, the transaction state should be marked for rollback - you should not be relying on persist to determine if merge should be called. Either call em.find, or just call em.merge on your entity and allow the JPA provider to determine if it should do an insert or an update for you.

Categories