How to refresh relationship before deleting entity - java

I want to delete an entity that has a #OneToMany relationship with another one, set up like this:
public class Dealership implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "dealership", orphanRemoval = true)
private Set<Car> cars;
}
public class Car implements Serializable {
#ManyToOne
#JoinColumn(name="co_id")
private Dealership dealership;
}
The thing is that, when I delete the dealership, I want to delete only the cars that were not sold. And no matter what I try, Hibernate deletes ALL cars tied with the dealership, through the cascade. Here's what I've been trying. In this example, I'm trying to transfer the sold cars to another dealership, and then I delete the dealership. This is supposed to delete the dealership, its employees, and ONLY the cars that were not sold:
Session session = SessionManager.getSession();
Transaction tx = session.beginTransaction();
Dealership dealershipToDelete = (Dealership) session.load(Dealership.class, idDealership);
for(Car c: dealershipToDelete.getCars().stream().filter(c -> c.isSold()).toArray(Car[]::new)){
Dealership newDealership = (Dealership) session.load(Dealership.class, idNewDealership);
c.setDealership(newDealership);
dealershipToDelete.getCars().remove(c);
}
session.update(dealershipToDelete);
session.flush();
session.delete(dealershipToDelete);
tx.commit();
session.close();
But it always deletes ALL cars. Even when I manage to make Hibernate update the cars with the new dealership. It updates them, and then it deletes them. Help would be greatly appreciated. Thanks.

Just refresh() the Dealership object before deleting it to reflect the changes made to its relationship with the Car class.Have slightly modified your code,try this:
Dealership dealershipToDelete = (Dealership) session.load(Dealership.class, idDealership);
Dealership newDealership = (Dealership) session.load(Dealership.class, idNewDealership);
for(Car c: dealershipToDelete.getCars().stream().filter(c -> c.isSold()).toArray(Car[]::new)){
c.setDealership(newDealership);
newDealership.getCars().add(c);
}
session.flush(); //this will flush the updates to sold Car, with the new Dealership details
session.refresh(dealershipToDelete); //this will load the updated "dealershipToDelete" without the 'Sold Car' object,the 'Unsold' ones will still be there
session.delete(dealershipToDelete); //this will delete the Dealership and its related unsold car objects.
tx.commit();
session.close();

you might try something like this :
first set the foreign key to nullable :
public class Car implements Serializable {
#ManyToOne
#JoinColumn(name="co_id" , nullable = true)
private Dealership dealership;
}
then you get the Id of dealershipToRemove:
int id = dealershipToRemove.getId();
then you delete all cars that have as dealership with the given id
query = session.createNativeQuery("delete from cars where co_id = :id and date is null");
query.setParameter(1,id);
query.executeUpdate();
then you break the relationship between dealershipToRemove and its cars :
dealershipToRemove .setCars(null);
session.remove(dealershipToRemove);

Related

Foreign key in hibernate, why create objects, how to update a record in referenced entity

I'm new to Hibernate environment. I have a basic question and I'm still trying to understand why people are picking Hibernate over SQL.
I have two tables, lets say one is user, one is a book.
A user can has many books, but a book only has a one owner.
If I used SQL, I try to write at
Table User,
int uid
PRIMARY KEY (uid)
Table Book,
int bid
int oid //ownerid
PRIMARY KEY (bid)
FOREIGN KEY (oid) REFERENCES User(uid)
I couldn't do this in Hibernate. I've tried the following:
Generated a user table without any relation, only #Id annotation for uid.
#Entity
#Table(name="USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "uid")
private Long uid;
public Long getUid()
{ return uid;}
}
Generated a book table, but I cannot understand this point. Everything on the internet says that I need to use #ManyToOne and #JoinColumns. If I use them I need to have an object:
#Entity
#Table(name = "BOOK")
public class Book{
#ManyToOne
#JoinColumn(name="uid",
referencedColumnName = "What should I write here?")
#Column(name ="oid") //owner id
private User user;
}
1- Why would I need to create a user object for my uid in book table? I just want to store a userid in my books table with Foreign Key constraint (If the user does not exist, then the book cannot be exist either)
2- Also, if I want to reach the userid of the owner, should I use this:
public Long getOwnerId()
{
return user.getUid();
}
3- If I want to change the owner of the book, what should I do? Since there is a whole user member in my book object, how can I update only the ownerid in my book table?
Why would I need to create a user object for my uid in book table?
You don't need to change anything to your database table. Hibernate will read the oid from the book table, and will populate the Book.user field with the object of type User identified by this oid.
That way, when you display the information about a book in your application for example, and you want the name of the owner to be displayed, all you need to do is
display(book.getUser().getName());
Instead of having to get the ID of the user, then execute a second database query to get the user.
if I want to reach the userid of the owner, should I use this:
yes. Or you don't ad any method, because any caller is able to do book.getUser().getUid() by itself.
If I want to change the owner of the book, what should I do?
Assuming you have the uid of the new owner, you would get the user identified by this ID, and set in on the book:
User newOwner = entityManager.find(User.class, newOwnerId);
// or User newOwner = entityManager.getReference(User.class, newOwnerId);
// depending on what you prefer
book.setUser(newOwner);
Regarding your mapping of the ManyToOne, it's wrong. You need to read the Hibernate manual, and the javadoc, instead of trying random things. It should simply be
#ManyToOne
#JoinColumn(name = "oid")
private User user;
In a ManyToOne Entity you just need to specify the name of yourforeign key inside the #JoinColumn annotation, like this:
#Entity
#Table(name = "BOOK")
public class Book{
//private Long bid;
#ManyToOne
#JoinColumn(name="oid")
private User user;
}
The referencedColumnName paramether is most used for ManyToMany relationships.
Hibernate is a object relational mapper to represent the table based data structure of a relational database in a usual object oriented way, nothing more or less.
You dont have to use annotations like #ManyToOne, #OneToMany, etc. You can just do this:
#Entity
#Table(name="USER")
public class User {
#Id
#Column(name = "uid")
private Long uid;
// setters and getters
}
#Entity
#Table(name = "BOOK")
public class Book {
#Id
#Column(name = "bid")
private Long bid;
#Column(name = "oid")
private Long oid;
// setters and getters
}
EntityManager em;
Long uid = em.createQuery("SELECT u.uid FROM User u WHERE ....", Long.class).getSingleResult();
book.setUid(uid);
Most developers dont want to handle with that much withs IDs and database access etc. They just want to access their business model in a natural object oriented way and there hibernate or JPA comes in:
EntityManager em;
User user1 = em.find(User.class, 1L);
Book book1 = new Book();
book1.setUser(user1);
You dont have to create a User instance for every single Book instance. Just reuse the existing User instance.
In your Example i would do the following:
#Entity
#Table(name="USER")
public class User {
// ...
#OneToMany(mappedBy = "user")
private Collection<Book> books = new ArrayList<>();
public void add(Book book) {
book.setUser(this);
books.add(book);
}
public void remove(Book book) {
book.setUser(null);
books.remove(book);
}
}
#Entity
#Table(name = "BOOK")
public class Book {
// ...
#ManyToOne
#JoinColumn(name="oid", referencedColumnName = "uid")
private User user;
}
EntityManager em;
User user1 = em.find(User.class, 1L);
Book book1 = new Book();
Book book2 = new Book();
user1.addBook(book1);
user1.addBook(book2);
book1.getUser(); // user1
book2.getUser(); // user1
Yes, if u want to handle with bare IDs
Set another User via setter. The corresponding uid in the book table will be updated.
book1.setUser(user2);
will result in the statement
update BOOK set oid = 2 where bid = 1;

Spring JPA #OneToMany not persisting the children

Before I start I checked few posts and none of them resolved my problem.
Please can someone guide me here. I wanted to establish a Plant(1) to Inventories(n) Relationship.
I created couple of models, one for Plant as below where I mention the OneToMany relationship
#Entity
public class Plant implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long plantID;
#OneToMany(mappedBy = "plant", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Inventory> listInventory = new ArrayList<>();
getter and setter....
And another one for Inventory where I mention ManyToOne relationship
#Entity
public class Inventory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long inventoryId;
#ManyToOne
#JoinColumn(name = "plantID", nullable = false)
private Plant plant;
When I try to persist the data like below, it saves the plant (parent) but not its children's.
As part of persisting the data, I did something like below
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
inventory.setPlant(plant);
listInventory.add(inventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
}
After reading few posts I found that I should set the association of plant with the inventory before persisting. So I did added the code for the same but it went on infinite or just my STS hanged.
I am sure I am doing something wrong but not sure where. Please can someone guide me through.
My expectation is that Inventory will be a reference table where I will have list of inventories. When I add a new plant, I will map few of the inventories to this plant. Similarly this will happen for many plants and many inventories.
As part of persistence, I will have to save plant and its inventories. I should also be able to pass the plant ID and retrieve the corresponding Inventories.
UPDATE 1:
Also I am not sure if the relation I am using is fair enough for this scenario. As my inventory is a reference table and at the same time, when a plant is mapped to multiple inventories, each inventory can be modified before persisting.
I tried #ManyToMany and it stores the relation in a 3rd table with a unique reference to both the tables but I wont be able to get the details of each inventory record.
With #ManyToMany, when I fetch it bring the values from the reference table and not the modified one which was persisted with the parent(plant) Please any advise
UPDATE 2
I tried with the same models but I changed the way I persist the data as below.
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
plant.addInventoryToPlant(inventory);
}
return plantRepository.save(plant);
}
Here is the add method in my plant model
public void addInventoryToPlant(Inventory inventory) {
listInventory.add(inventory);
inventory.setPlant(this);
}
It is just overwriting the inventory table with different plant IDs but not creating a reference table or join table to maintain all the possible mappings. If I try to add a plant with 2 inventories, it maps them first to the Inventory table. If I add another plant then this is getting overwritten. I was in an assumption that it will create a third table to maintain this entity
I have a feeling that those Inventory entities are detached and the Persistence Provider is not considering them during flush. You do not get an exception as #OneToMany is a special kind of relationship in flush algorithm because the act of persisting the owning entity does not depend on the target thus Hibernate will proceed and persist only the Plant entity.
Try using merge instead of persist:
plant.setListInventory(listInventory);
return plantRepository.merge(plant);
Update
You can also merge each inventory one by one using save as Spring JPA implicitly checks whether entity should be saved or merged:
for (Inventory inventory : listMaterial) {
Inventory mergedInventory = inventoryRepository.save(inventory);
mergedInventory.setPlant(plant);
listInventory.add(mergedInventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
This answers the UPDATE 2 part:
As I understand, Plant and Inventory are in a many-to-many relationship, but there are additional properties that are to be stored along with the information that a specific Plant is holding a specific Inventory item.
In that case, you need an additional entity (let's call it StockItem) that will be used to hold that additional state. Both Plant and Inventory will then be in a one-to-many relationship with the new entity.
Your mapping will then become:
class Plant {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "plant_id")
private Set<StockItem> stockItems;
...
}
class StockItem {
#ManyToOne(mappedBy = "stockItems")
private Plant plant;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "inventory_id")
private Inventory inventory;
//put any fields here that may vary from one plant to plant to another
private int quantity;
}
class Inventory {
#OneToMany(mappedBy = "inventory")
public Set<StockItem> stockItems;
// leave any fields that will NOT vary from one plant to another here
private String name;
}
Things to note:
This mapping assumes that you will be adding new StockItems to a Plant (new items added to Inventory.stockItems will be ignored by JPA), in which case it will be enough to set the StockItem.inventory field to a proper value, add the StockItem to the Plant.stockItems list, and save the Plant entity
The StockItem.plant and Inventory.stockItems are not absolutely necessary, remove them if you do not need them

JPA nativeQuery returns cached resultList

I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.

Hibernate persist entity without fetching association object. just by id

I have an simple association between 2 entities:
public class Car {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
and
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long userId;
...
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Car> cars;
...
}
Then I get some user id from client. For example, userId == 5;
To save car with user I need to do next:
User user = ... .findOne(userId);
Car car = new Car();
car.setUser(user);
... .save(car);
My question is: Can I persist car record without fetching user?
Similarly like I would do by using native SQL query: just insert userId like string(long) in Car table.
With 2nd lvl cache it will be faster but in my opinion I don't need to do extra movements. The main reason that I don't want to use native Query is because I have much more difficult associations in my project and I need to .save(car) multiple times. Also i don't want to manually control order of query executions.
If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?
Correct me if I'm wrong.
Thanks in advance!
UPDATE:
Actually the mapping is similar to:
There is #ManyToMany association between User and Car. But cross table is also an entity which is named, for example, Passanger. So the mapping is next:
public class User{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", targetEntity = Passenger.class)
private Set<Passenger> passengers;
}
Cross entity
#IdClass(value = PassengerPK.class)
public class Passenger {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "car_id")
private Car car;
... other fields ...
}
Car entity:
public class Car {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car", targetEntity = Passenger.class, cascade = CascadeType.ALL)
private Set<Passenger> passengers;
}
And the code:
List<User> users = ... .findInUserIds(userIds); // find user records where userId is IN userIds - collection with user Ids
Car car = new Car(); //initialization of car's fields is omitted
if (users != null) {
car.setPassengers(new HashSet<>(users.size()));
users.forEach((user) -> car.getPassengers().add(new Passenger(user, car)));
}
... .save(car);
"Can I persist car record without fetching user?"
Yes, that's one of the good sides of Hibernate proxies:
User user = entityManager.getReference(User.class, userId); // session.load() for native Session API
Car car = new Car();
car.setUser(user);
The key point here is to use EntityManager.getReference:
Get an instance, whose state may be lazily fetched.
Hibernate will just create the proxy based on the provided id, without fetching the entity from the database.
"If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?"
No, it will not. Queries are executed immediately.
If someone is using Spring Data JPA: The same can be achieved in Spring Data JPA can be done using the method
JpaRepository.getReferenceById(ID id)
This replaced the former
getOne(ID)
Hibernate users can implement this method:
public <T extends Object> T getReferenceObject(Class<T> clazz, Serializable id) {
return getCurrentSession().get(clazz, id);
}
And call like:
MyEntity myEntity = getRefererenceObject(MyEntity.class, 1);
You can change id type to Integer or Long as per your entity model.
Or T can be inherited from your BaseEntity if you have one base class for all entities.
The following approach works for me:
User user = new User();
user.setId(userId);
car.setUser(user);

Qualified OneToMany-ManyToOne relation

i try to build a sample qualified relation and get a null value for the qualifier column in the join table.
Error:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException:
cannot insert NULL into 'TIRE_POSITION'
Entities:
#Entity
public class Bike {
#OneToMany(mappedBy = "bike", cascade=CascadeType.ALL)
#MapKeyColumn(name="TIRE_POSITION", nullable=false)
private Map<String, Tire> tires;
...
}
#Entity
public class Tire {
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="BIKE_ID")
private Bike bike;
public enum TirePosition{
FRONT("FRONT"), BACK("BACK");
...
}
}
Insert invocation:
public void testIt(){
Bike bike = new Bike();
Tire tireFront = new Tire();
Tire tireBack = new Tire();
Map<String, Tire> tires = new HashMap<String, Tire>();
tires.put(Tire.TirePosition.BACK.getPosition(), tireBack);
tires.put(Tire.TirePosition.FRONT.getPosition(), tireFront);
bike.setTires(tires);
EntityTransaction trans = em.getTransaction();
trans.begin();
em.persist(bike);
trans.commit();
}
Anyone an idea what is wrong here?
Set both sides of your relationship and also remove the nullable=false constraint to see if the provider ends up setting the field. As JB Nizet pointed out, you are using a JoinColumn relationship instead of a relation table, which will force the "TIRE_POSITION" to be put in the Tire table; Some providers (EclipseLink) will insert the Tire table with all the data from the Tire entity and then later on update the reference with mapping data contained in other entities. Since the "TIRE_POSITION" field is mapped through Bike, it will get updated later. If so, you can either remove the database constraint or set your database to delay constraint processing until after the transaction.

Categories