My entities are:
the ID of device which is deiveID
has many-to-many relationship with
the ID of Lib which is rID
my test code is :
two new device entities want to set the same new libentity
the problem is :
if i use the same entitymanager to persist that 2 new device entities, it will be ok.
but if i use 2 different entitymanager instance to persist them ,the error"primary key violation" will come out. I think the entitymanger try to persist the libentity at the second time, which has already been persisted in the first time.
--------------deviceinfo entity ------------------------------------------------
#Entity
#Table(name="deviceInfo")
public class DeviceInfoEntity extends BaseEntity implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long deviceId;
....
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinTable(name = "device_lib", joinColumns = #JoinColumn(name = "deviceInfo_id",
referencedColumnName="deviceId"),
inverseJoinColumns = #JoinColumn(name = "lib_id", referencedColumnName="rId"))
private List<LibEntity> resourceList = null;
......
}
-------------------------lib entity ---------------------------------------------
#Entity
#Table(name="lib")
public class LibEntity extends BaseEntity implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long rId;
#ManyToMany(mappedBy = "resourceList", cascade=CascadeType.ALL,
fetch=FetchType.LAZY, targetEntity=DeviceInfoEntity.class)
private List<DeviceInfoEntity> deviceInfolist = null;
.....
}
my test code is:
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("testPU");
EntityManager em = emFactory.createEntityManager();
LibEntity libEntity = new LibEntity();
DeviceInfoEntity dEntity = new DeviceInfoEntity();
dEntity.setName("dadadada");
dEntity.setLibEntity(libEntity);
DeviceInfoEntity dEntity2 = new DeviceInfoEntity();
dEntity2.setName("dadadadadddddd");
dEntity2.setLibEntity(libEntity);
em.getTransaction().begin();
em.persist(dEntity);
em.getTransaction().commit();
em.close();
EntityManager em2 = emFactory.createEntityManager();
em2.getTransaction().begin();
em2.persist(dEntity2);
em2.getTransaction().commit();
it will have the error:
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.LIB(RID)"; SQL statement:
INSERT INTO lib (RID) VALUES (?) [23505-165]
but if i use the same EntityManager the error will not happen. Is there anyone know whats the reason? is that caused by cascade=CascadeType.ALL?
You are corrupting your persistence context by assign detached objects to managed objects. Managed object should only reference other managed objects.
For dEntity2 you should set the libEntity to the result of a find(), getReference() or merge() of libEntity.
i.e.
dEntity2.setLibEntity(em2.find(libEntity.getClass(), libEntity.getId());
You could probably also call merge() instead of persist() and it should resolve the detached objects.
Related
I have two entities mapped to one another using the oneToMany annotation. One entity is bookedBus and the second is drivers The drivers entity would already have a row inserted into that would later become a foreign reference (FK) to bookedBus entity(PK). Below are the two entities, setters and getter have been skipped for brevity.
First entity
#Entity
#Table(name = "bookedBuses")
public class BookedBuses implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "driver_id")
private Drivers driver;
}
Second entity
#Entity
public class Drivers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "driver")
private List<BookedBuses> bookedBus;
}
Now When I try to save to the booked bus entity it throws the following exception
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.bus.api.entity.Drivers; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.bus.api.entity.Drivers
Below is how I tried saving to the bookedBus entity
BookedBuses bookedRecord = new BookedBuses();
bookedRecord.setBookedSeats(1);
bookedRecord.setBookedBusState(BookedBusState.LOADING);
bookedRecord.setBus(busService.getBusByPlateNumber(booking.getPlateNumber()));
bookedRecord.setRoute(booking.getRoute());
infoLogger.info("GETTING DRIVER ID ======= " + booking.getDriver().getId());
Drivers drivers = new Drivers(booking.getDriver().getId());
List<BookedBuses> d_bu = new ArrayList<>();
drivers.setBooked(d_bu);
drivers.addBooked(bookedRecord);
bookedRecord.setDriver(drivers);
bookedBusService.save(bookedRecord);
My BookBusService Save Method as requested
#Autowired
private BookedBusRepository bookedBusRepo;
public boolean save(BookedBuses bookedRecord) {
try {
bookedBusRepo.save(bookedRecord);
return true;
} catch (DataIntegrityViolationException ex) {
System.out.println(ex);
AppConfig.LOGGER.error(ex);
return false;
// Log error message
}
}
1st you have some mix up in naming: you have Driver & Drivers. Like this:
private Drivers driver;
Also selecting variable names like this:
BookedBuses bookedRecord = new BookedBuses();
will cause a lot of confusion. Do not mix plural & singular between types and preferably also do not introduce names that might not be easily associated like record. Also this:
private List<BookedBuses> bookedBus;
which should rather be like:
private List<BookedBus> bookedBuses;
(and would alsoi require change to your class name BookedBuses -> BookedBus)
Anyway the actual problem seems to lie here:
Drivers drivers = new Drivers(booking.getDriver().getId());
You need to fetch existing entity by id with a help of repository instead of creating a new one with id of existing. So something like:
Drivers drivers = driverRepo.findOne(booking.getDriver().getId()); // or findById(..)
It seems that you have a constructor (that you did not show) that enables to create a driver with id. That is not managed it is considered as detached. (You also have drivers.addBooked(bookedRecord); which you did not share but maybe it is trivial)
Note also some posts suggest to changeCascadeType.ALL to CascadeType.MERGE whether that works depends on your needs. Spring data is able to do some merging on save(..) based on entity id but not necessarily in this case.
This line
Drivers drivers = new Drivers(booking.getDriver().getId());
If you already have the driver ID available with you then there's no need to pull the driver ID again from the DB.
After removing the Cascade attribute from #OneToMany & #ManyToOne your code should work.
#Entity
#Table(name = "bookedBuses")
public class BookedBuses implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
`
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "driver_id")
private Drivers driver;
}
#Entity
public class Drivers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "driver_id")
private List<BookedBuses> bookedBus;
}
The database table structure is the following:
id INT
extId VARCHAR
name VARCHAR
parent INT (references self.id)
Here is the entity
#Entity
#Table(name = "categories")
public class Category
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "parent", referencedColumnName = "id")
private Category parent;
#org.hibernate.annotations.OrderBy(clause = "name ASC")
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", cascade = CascadeType.ALL)
private Set<Category> children = new HashSet<>();
#Column(name = "extId")
private String extId;
#Column(name = "name")
private String name;
public void addChild(Category child)
{
child.setParent(this);
this.children.add(child);
}
//getters and setters ...
}
In the very beginning there is only one entity:
{
id: 0
extId: ''
name: 'Category'
parent: null
children: Set<Category>{/*empty*/}
}
This entity is fetched in the beginning of program and assigned to another class
Later this class performs addition of new Category as a child to existing root (the one that was fetched in the beginning) property
Addition of child is done like this:
Session session = HibernateSessionFactory.getInstance().getFactory().openSession();
//gather data
Category child = new Category();
//set name
//set extId
this.rootCategory.addChild(child);
Transaction tx = session.beginTransaction();
session.save(this.rootCategory);
tx.commit();
session.close();
After this instead of expected result in database:
Root(id(0), extId(''), parent(null), name('root'))
\-— Child(id(10), extId('art-0'), parent(0), name('child'))
I get the following result
Root(id(0), extId(''), parent(null), name('root'));
Root(id(10), extId(''), parent(null), name('root'))
\-— Child(id(11), extId('art-0'), parent(10), name('child'))
Notes:
New Session is created for each action and this session is acquired via Singleton SessionFactory
If I refresh() root entity before adding a child -- everything is OK, no duplicates
If I perform child addition immediately after fetching root entity -- no duplicates
What could be the reason of this behavior (assuming two different sessions)? And how can it be fixed?
After fetching from db at the beginning of your program, rootCategory becomes a detached object.
Later when you want to use it in another session, you need to reattach it to this session. Instead of session.save(), you can use session.update()
this.rootCategory.addChild(child);
Transaction tx = session.beginTransaction();
session.update(this.rootCategory);
tx.commit();
session.close();
If you are closing the session after fetching rootCategory then the rootCategory object becomes a detached object according to the hibernate lifecycle and using Session.save() on it will create a new row for it. Therefore you either need to fetch, add the child and save the rootCategory object in the same session or use refresh to tell hibernate that it is not a new object but one that is already saved.
I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.
I am doing Many To One relationship using JPA . While deleting child object from Child table it's throwing exception.
Below is My code:
Project.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="projectName")
private String projectName;
#Column(name="projectDesc")
private String projectDesc;
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="companyId")
Company.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="compName")
private String compName;
#Column(name="address")
private String address;
Below is Insert code:
InserAction.java
public static void main(String[] args) {
Company comp2 = new Company();
comp2.setCompName("IBM");
comp2.setAddress("Bangalore");
Project pro2 = new Project();
pro2.setProjectName("Management System");
pro2.setProjectDesc("System");
pro2.setCompany(comp2);
EntityManager entityManager = EntityManagerUtil.getEmf().createEntityManager();
try{
EntityTransaction entr = entityManager.getTransaction();
entr.begin();
entityManager.persist(pro2);
entr.commit();
}
}
DeleteAction.java
EntityManager entityManager = EntityManagerUtil.getEmf()
.createEntityManager();
try {
EntityTransaction entr = entityManager.getTransaction();
entr.begin();
Project project = entityManager.find(Project.class,5);
entityManager.remove(project);
entr.commit();
}
Exception is
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`prabha`.`project`, CONSTRAINT `FK_project_companyId` FOREIGN KEY (`companyId`) REFERENCES `company` (`id`))
Error Code: 1451
Call: DELETE FROM company WHERE (id = ?)
bind => [1 parameter bound]
Query: DeleteObjectQuery(com.demo.manytoone.Company#301db5ec)
While deleting project object from Project table it' throwing above exception how can I over come this.
#ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST}, fetch=FetchType.EAGER)
The above code will resolve your issue. If you observe source code of annotation ManyToOne it has an array of cascade type so you can map multiple cascade types
You should not use CascadeType.ALL, try using CascadeType.MERGE
The meaning of CascadeType.ALL is that the persistence will propagate (cascade) all EntityManager operations (PERSIST, REMOVE, REFRESH, MERGE, DETACH) to the relating entities.
It seems in your case to be a bad idea, as removing an Project would lead to removing the related Company when you are using CAscadeType.ALL. As a Company can have multiple projects, the other projects would become orphans. However the inverse case (removing the Company) would make sense - it is safe to propagate the removal of all projects belonging to a Company if this company is deleted.
You can also use various CascadeTypes, for e.g.
cascade = {CascadeType.PERSIST,CascadeType.MERGE}.
So use all those that applied to you.
For more info.
You got MySQLIntegrityConstraintViolationException. It means there are tables in the database you're binding. You should map when you set up the many to one relationship tables. Company should be able to get more than one project. So you have to define the list of projects.
Project.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="projectName")
private String projectName;
#Column(name="projectDesc")
private String projectDesc;
#ManyToOne
#JoinColumn(name="companyId")
private Company company;
Company.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="compName")
private String compName;
#Column(name="address")
private String address;
#OneToMany(mappedBy="company", fetch=FetchType.EAGER)
private List<Project> projects;
I have two entities, a Competition and a Team:
Competition:
#Entity
public class Team implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
int id;
#Column(length=255)
String name;
#ManyToOne(optional=true)
Competition nextCompetitionAttending;
List<SiteUser> teamMembers;
nextComeptitionAttending is set by the following method:
public void setNextCompetitionAttending(Competition nextCompetitionAttending) {
this.nextCompetitionAttending = nextCompetitionAttending;
nextCompetitionAttending.addTeam(this);
}
Competition is defined thusly:
#Entity
public class Competition implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
int maxTeamSize;
String name;
#OneToMany(mappedBy="nextCompetitionAttending" )
List<Team> teamsAttending;
My persistance code looks like this:
utx.begin();
EntityManager em = emf.createEntityManager();
em.merge(user);
Competition c = em.find(Competition.class, id);
Team teamon = user.getTeamOn();
teamon.setNextCompetitionAttending(c);
System.out.println("Set next");
em.flush();
utx.commit();
em = emf.createEntityManager();
Team t = em.find(Team.class, teamon.getId());
System.out.println(t.getNextCompetitionAttending());
In the database, the column "NEXTCOMPETITIONATTENDING_ID" is null, as is the println at the end of the code which I've been using to try to debug this.
Why doesn't this persist?
The culprit is probably this line:
em.merge(user);
user is a detached entity, and you merge it. Then, you get the team from the user. But merge doesn't make the detached entity attached. It loads the attached user, copy the state from the detached one to the attached one, and returns the attached one. So, all the subsequent changes you're doing are done to the detached entity.
Replace this line with
user = em.merge(user);