Simplifying, in my database I have tables:
Car (pk="id_car")
CarAddon (pk="id_car_fk,id_addon_fk",
`FK_car_addon_addon` FOREIGN KEY (`id_addon_fk`) REFERENCES `addon` (`id_addon`)
`FK_car_addon_car` FOREIGN KEY (`id_car_fk`) REFERENCES `car` (`id_car`)
Addon (pk="id_addon")
Shortly: I have cars, many cars can has many addons (like ABS etc).
There are tables with cars, addons, and one table which is logical connection.
Overall, entities work fine. I have no problems with persist data, when I want persist single object. I don't have problems, when I want FETCH data, ie. Car->getAddon();
But, when I'm going to persisting a collection, nothing happens. No exceptions were thrown, there were no new data in database.
//DBManager is a singleton to create an EntityManager
EntityManager em = DBManager.getManager().createEntityManager();
em.getTransaction().begin();
Addon addon1 = new Addon();
addon1.setName("czesc1");
em.persist(addon1);
Addon addon2 = new Addon();
addon2.setName("czesc2");
em.persist(addon2);
car.setAddonCollection(new ArrayList<Addon>());
car.getAddonCollection().add(addon1);
car.getAddonCollection().add(addon2);
em.persist(car);
em.getTransaction().commit();
In this case, addons were stored in Addon table, car in Car table. There are no new data in CarAddon table though object car has good data (there is addon collection in debbuger).
When I changed em.persist(car) to em.merge(car) I got an exception:
"SEVERE: Persistence error in /admin/AddAuction : java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: model.entity.Car[ idCar=0 ]."
Simple version of my classess:
#Entity
#Table(name = "addon")
#XmlRootElement
#NamedQueries({...})
public class Addon implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_addon")
private Integer idAddon;
#Size(max = 100)
#Column(name = "name")
private String name;
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
#ManyToMany
private List<Car> carCollection;
#XmlTransient
public List<Car> getCarCollection() {
return carCollection;
}
public void setCarCollection(List<Car> carCollection) {
this.carCollection = carCollection;
}
}
#Entity
#Table(name = "car")
#XmlRootElement
#NamedQueries({...)
public class Car implements Serializable {
#ManyToMany(mappedBy = "carCollection", fetch= FetchType.EAGER, cascade=CascadeType.ALL)
private List<Addon> addonCollection;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_car")
private Integer idCar;
#XmlTransient
public List<Addon> getAddonCollection() {
return addonCollection;
}
public void setAddonCollection(List<Addon> addonCollection) {
this.addonCollection = addonCollection;
}
}
How can I fix it?
ps1. I have:
cascade=CascadeType.ALL przy #ManyToMany private List<Car> carCollection
but this dos not solve my problem.
ps2. I am using Netbeans 7, EclipseLink and MySQL (not Hibernate - I have problem with it)
I have one theory that always seems to trip people up with many-to-many collections. The problem is that in memory, the associations are made in two places. Both in the car's addons list and in the addon's cars list. In the database, there isn't such a duplication.
The way JPA providers get around this is through the mappedBy attribute. Since you have mappedBy on the car's addons list this means that the relationship is actually controlled by the addon's cars list (confusing I know).
Try adding the following:
addon1.setCarCollection(new ArrayList<Car>());
addon1.getCarCollection().add(car);
addon2.setCarCollection(new ArrayList<Car>());
addon2.getCarCollection().add(car);
before you persist the car.
Generally speaking, I would avoid many-to-many associations. What you really have is an intermediate link table, with a one-to-many and a many-to-one. As soon as you add anything of interest to that link table (e.g. datestamp for when the association was made), poof, you are no longer working with a pure many-to-many. Add in the confusion around the "owner" of the association, and you're just making things a lot harder than they should be.
could you try add
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
to both side
just reverse the joinColumns and inverseJoinColumns
Try adding (fetch = FetchType.EAGER) to your ManyToMany annotation
Related
Those are my classes. I want to delete items from likedCourses. So I expect JPA to delete item from course_like table. I looked and try to understand from other examples but couldn't. It is like deletion doesn't exist in JPA realm when there is relation. It is good for selecting though. I'd want to share what I tried but I couldn't find anything about it.
Note : I see that in ManyToMany relationship there is not orphanRemoval option.
#Entity
class Student {
#Id
Long id;
#ManyToMany
#JoinTable(
name = "course_like",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "course_id"))
Set<Course> likedCourses
}
#Entity
class Course {
#Id
Long id;
#ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;
}
If you want to remove an entry from the course_like table, you will have to load the Student and remove the element from the likedCourses set that should be removed. If you do that in a #Transactional method, you will see that Hibernate will emit delete statements to delete the rows that represent the removed objects from the likedCourses set. That's the magic of an ORM, it synchronizes the object graph with the database, without you telling it to emit statement A, B, ...
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;
}
I have 3 tables that have a hierarchical relationship:
Page (Grandmother)
public class Page extends BaseDAO {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "page_id", unique = true, nullable = false)
public Integer getPageId() {
return this.pageId;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "page", cascade=CascadeType.ALL, orphanRemoval=true)
#NotFound(action = NotFoundAction.IGNORE)
public Set<PageWell> getPageWells() {
return this.pageWells;
}
}
PageWell (Mother)
public class PageWell extends BaseDAO {
#Id
#Column(name = "page_well_id", unique = true, nullable = false)
public int getPageWellId() {
return this.pageWellId;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "page_id", nullable = false)
public Page getPage() {
return this.page;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pageWell", cascade=CascadeType.ALL)
public Set<PageComponentAttribute> getPageComponentAttributes() {
return this.pageComponentAttributes;
}
}
PageComponentAttribute (Daughter)
public class PageComponentAttribute extends BaseDAO {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "page_component_attribute_id", unique = true, nullable = false)
public Integer getPageComponentAttributeId() {
return this.pageComponentAttributeId;
}
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name = "page_well_id", nullable = false)
public PageWell getPageWell() {
return this.pageWell;
}
}
The primary keys for all three tables are AutoIncrement in MySQL. The expected behavior is that when I save the Page, all PageWell objects get saved, and all PageComponentAttribute objects also get saved.
For some reason, it is working correctly for the Grandmonther -> Daughter relationship. But in the case of the Mother -> Daughter relationship, the Daughter's foreign key is set to 0 every time. This was obviously causing a constraint violation. I have temporarily removed the FK constraint on that relationship, and the record makes it into the table, but the FK is still 0.
My save code looks like this:
Page page = getPage(request); //getPage() finds an instance of page, or creates and persists a new instance if none exists.
Set<PageWell> wells = page.getPageWells();
wells.clear(); //delete all related PageWell objects so we can re-create them from scratch
page = pageHome.merge(page);
wells = page.getPageWells();
PageWell pageWell;
// Now create a new PageWell and set up bi-directonal mapping with Page. This part works great.
pageWell = new PageWell();
pageWell.setPage(page);
wells.add(pageWell);
// Now do the exact same thing with the PageComponentAttribute objects
PageComponentAttribute pca = new PageComponentAttribute();
pca.setPageWell(pageWell);
pca.getPageWell().getPageComponentAttributes().add(pca);
// Now save the Page
page = pageHome.merge(page);
When I check the database, the FK in the PageComponentAttribute table is set to 0. Again, I have temporarily removed the FK constraint from MySQL just to allow the record to save without an exception, but besides that, what am I doing wrong?
I would try to do one of the things, or all:
1) Remove the cascade from the #ManyToOne. In general thats not a good idea to have it configured like that. It essentially makes sense only for #OneToMany and #OneToOne.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "page_well_id", nullable = false)
public PageWell getPageWell() {
return this.pageWell;
}
2) Try using the Hibernate cascade configuration instead of the JPA one:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pageWell")
#Cascade(CascadeType.ALL)
public Set<PageComponentAttribute> getPageComponentAttributes() {
return this.pageComponentAttributes;
}
There may be some small differences, see: article
3) Not sure why you invoke merge twice on the page entity. I would just stick to one at the very end.
4) Last workaround that comes to my mind would be performing an explicit flush here:
pageWell = new PageWell();
pageWell.setPage(page);
wells.add(pageWell);
session.flush();
and then:
PageComponentAttribute pca = new PageComponentAttribute();
pca.setPageWell(pageWell);
pca.getPageWell().getPageComponentAttributes().add(pca);
session.merge(pageWell);
In theory, pageWell should have the primary already generated because of the flush and it should not be 0 anymore.
I wish i had a testing environment right now to test this properly.
In the unlikely chance that someone has made the same bone-headed mistake I've made, the problem was that the PageWell entity's primary key didn't have a Generation strategy. I added that and it fixed my problem.
#GeneratedValue(strategy = IDENTITY)
I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.
I have a the two following classes:
#Entity
class A {
#Id
private aId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "AB", joinColumns = #JoinColumn(name = "aId", referencedColumnName = "aId"), inverseJoinColumns = #JoinColumn(name = "bId", referencedColumnName = "bId"))
private Set<B> bSet;
}
#Entity
class B {
#Id
private bId;
}
I load the complete object structure from one database and then enters a new transaction on the second database to persist the structure again. However the "AB" table is left empty. This is very strange as "B" is persisted though I only explicitly persist "A". I have check that A-objects contains non empty sets of B, so that is not a problem.
This leaves me with the conclusion that Hibernate believes that "AB"-table should exist as both "A" and "B" already have their primary keys. Is there a way around this so I can get Hibernate to persist the join-table in the second database?
I guess this is happening because you are using proxy object.That is if you create instances of A and B with new operator and then call persist ,Join table record will be created .But you are using object from obtained from entitymanager(these are proxy objects) so you have to merge object that way entitymanager will create new proxies of this objects.