Hibernate 2 records being inserted for Single JAVA Object - spring

I am using Hibernate Implementation of JPA with Spring.
Class Country{
#OneToMany(mappedBy="Country", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<State> stateList;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "Current_State_ID")
State currnetState;
}
Class State{
#ManyToOne
#JoinColumn(name="Country_ID")
private Country country;
}
State stateObj = new State();
country.getStateList().add(stateObj);
country.setCurrnetState(stateObj);
countryRepository.saveAndFlush(country);
countryRepository is a JPA Repository Implemenntation.
This creates 2 entries for in State Table, which messes up my logic. Can someone please point me what I am doing worng.

I am not sure why but changing my code to following works for me.
List<State> stateList = new ArrayList<State>();
stateList.add(state);
country.setStateList(stateList);
Creating a New List instance and setting it to country.

Related

Spring JPA delete entity not working when iterating

I am trying to delete an entity using its remove method of its repository from another service class, but it is not getting deleted. Below code works when I hard code the Id:
long id = 1234;
Optional<Employee> employeeOptional = employeeRepository.findById(id);
Employee employee = employeeOptional.get();
employeeRepository.delete(employee);
Above code is working fine, but if I try with below code, deletion is not happening.
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employeeRepository.delete(employee);
}
}
I am trying the code from DepartmentServiceImpl class, but above is not working, but same when id is hardcoded it works.
Inside Department I have relationship like below,
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "deal")
private Set<Employee> employees= new HashSet<>();
And inside Employee I have like below,
#ManyToOne
#JoinColumn(name = "department_id", referencedColumnName = "department_id")
private Department department;
How can I fix this issue?
You are attempting to delete Employees, but your entities still have references to each other.
A better way to delete an employee is to use orphan removal and remove the employee from the collection which will trigger a delete.
Also mappedBy = "deal" should be the name of the attribute on the owning side of the relationship so this should be mappedBy = "department"
#OneToMany(
cascade = CascadeType.ALL,
mappedBy = "department",
orphanRemoval = true
)
private Set<Employee> employees= new HashSet<>();
add a method to Department to remove the Employee and keep the bidirectional relationship in sync by also removing Department from Employee
public void removeEmployee(Employee employee) {
employees.removeEmployee(employee);
employee.setDepartment(null);
}
you can then remove the 'Managers' from your Employees collection which should trigger the delete statements
List<Employee> managers = department.getEmployees.stream()
.filter(e -> e.getRole().equals("Manager"))
.collect(Collectors.toList());
for (Employee manager : managers) {
department.removeEmployee(manager);
}
Not tested but should work fine:
Try tweaking your code a little like this:
Set<Employee>employees= new HashSet<>();
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employees.add(employee);
}
}
department.setEmployees(employees);//I assume you have a setter
departmentRepository.save(department); //I hope you understand what departmentRepository means
Here you are reassigning the valid employees list.
You could follow another method, instead of deleting each entity separately, you could call a bulk-save using saveAll(...) method on the valid list.

#Batchsize annotation not working for OneToMany

I have following classes and on annotating #BatchSize annotation it is not working and I am getting n+1 select query.
Class Shipment{
#OneToMany(fetch = FetchType.LAZY, mappedBy = order.shipment, cascade = CascadeType.ALL,
orphanRemoval = true)
#BatchSize(size=20)
Set<Orders> orders = new Hashset(); <---- Batch size annotation not working
}
Order.class
class Order{
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "item_fk")
Item item;
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "shipment_fk")
Shipment shipment; }
Item.class
class Item{
String id;
String name;
}
What is mistake in implementation that i am getting n+1 queries?
Try to use List<Orders> instead of Set<Orders>.
Please note as it's mentioned in the documentation:
However, although #BatchSize is better than running into an N+1 query issue, most of the time, a DTO projection or a JOIN FETCH is a much better alternative since it allows you to fetch all the required data with a single query.
Your N + 1 query issue is due to the fact that you do eager fetching of Item in Order. Change to LAZY there and you should be good to go.

JPA does not refresh related entity after insert

I have issues with some two entities, that have relation to same entity.
So, I have Transaction (do not confuse it with session transaction), TransactionItem, Customer and Subscription. Both, Transaction and Customer has relation Subscription.
Transaction.java
#OneToMany(
mappedBy = "transaction",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true
)
private List<TransactionItem> transactionItems;
TransactionItem.java
#OneToOne(mappedBy = "transactionItem", cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
private Subscription subscription;
Customer.java
#OneToMany(
mappedBy = "customer",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true
)
private List<Subscription> subscriptions;
Subscription.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id", referencedColumnName = "guid")
private Customer customer;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "transaction_id", referencedColumnName = "guid")
private TransactionItem transactionItem;
I add TransactionItem to Transaction and then Subscription to TransactionItem. Nothin special really.
When Transaction is being saved hibernate save all dependant relations as well, which is fine and expected.
The issue is when I try to refresh Customer, that have one more Subscription attached, I get old entity. I tried to flush, evict, clear ... but nothing seems to work. I tried to search for the solution but I found nothing, that could help me out with it.
I am working with spring boot 2.1.1
Thanks for help!
What I had to do was to clear the persistence context of EntityManager. Usually with using jpa you do not want to do this manualy but in this case I had no other choice.
//aquire instance of EntityManager in your bean
#Autowired
private EntityManager entityManager;
//then when everything is saved we need to flush the changes and then clear context
entityManager.flush();
/**
*
* From the javax.persistence.EntityManager documentation:
*
* Clear the persistence context, causing all managed
* entities to become detached. Changes made to entities that
* have not been flushed to the database will not be
* persisted.
*/
entityManager.clear();

"could not initialize proxy - no Session" For Multiple ManyToMany relationships in the parent

I have a Parent User Class that has multiple ManyToMany Relationships.
#Table(name = "user")
public class User {
..
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "user_address",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "address_id")}
)
#JsonIgnore
private final List<Address> addresses = new ArrayList<Address>();
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "reports",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "reports_id")}
)
#JsonIgnore
private final List<Reports> reports = new ArrayList<Reports>();
}
When I access the FIRST ManyToMany property, everything works fine. However, immediately after
accessing the first, when I try to access the SECOND ManyToMany Property I get the "could not initialize proxy - no Session" exception:
#Component
public class Combiner {
public void combineData() {
...
List<Address> addresses = user.getAddress(); // This works
List<Reports> reports = user.getReports(); // Get the error here
..
}
}
The Address and Reports classes have the inverse relationship as many ManyToMany back to the User Entity Above.
public class Address {
#ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
public class Reports {
#ManyToMany(mappedBy = "reports", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
I tried searching SO for the same error where there are MULTIPLE relationships like mine and the first passes but second fails, but could'nt find a post (or google couldn't understand the search terms, if anyone knows a pre-existing one - please let me know).
Could someone assess what else Im missing?
I've tried these so far to no avail:
Added #Transactional to the parent Service class that calls Combiner above
Made the second failing relationship EAGER. (as i understand it you cant make BOTH EAGER since i get a multiple bags error probably because of Cartesian join)
AM Using SpringBoot (2.2.4) with Hibernate Core {5.4.10.Final}
Approach one:
Make #ManyToMany uni-directional. The exception clearly says it can not initialize the collection of role you have in User class.
As you asked in the comment section Why can't this use case be Bi Directional - You can make this bi-directional as well.
Approach two: make collection of role EAGER or use Hibernate.initialize() to initialize the collection.
Bonus: you can make both collection EAGER by using Set not List.

JPA and Hibernate Cascade DELETE OneToMany does not work

I've been reading post after post and article after article trying to get cascade deletes to work with JPA/Hibernate in the latest Spring Boot version. I've read that You have to use Hibernate specific cascades and I've read that you don't. I've read that they just don't work but it seems to be a mixed bag. Everything I've tried doesn't work. The relationship is bi-directional.
Doesn't Work:
#Entity
public class Brand {
#OneToMany(mappedBy = "brand", orphanRemoval = true, fetch = FetchType.LAZY)
#Cascade({CascadeType.DELETE})
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;
}
Doesn't Work:
#Entity
public class Brand {
#OneToMany(mappedBy = "brand", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;
}
Does anything work other than deleting the TaxRates prior to deleting the Brand ?
My test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {Application.class, SpringSecurityConfig.class})
#ActiveProfiles("test")
#Transactional
public class CascadeTests {
#Autowired
private BrandService brandService;
#Autowired
private TaxRateLoaderService taxRateLoaderService;
#Autowired
private TaxRateService taxRateService;
#Autowired
private TaxRateRepository taxRateRepository;
#Autowired
private BrandRepository brandRepository;
#Test
public void testCascadeWorks() throws Exception {
taxRateLoaderService.loadData(null, 10);
// if I uncomment this then I'm good
// but shouldn't have to if cascade works
//taxRateService.deleteAll();
brandService.deleteAll();
List<TaxRate> rates = Lists.newArrayList(taxRateRepository.findAll());
List<Brand> brands = Lists.newArrayList(brandRepository.findAll());
Assert.assertEquals(rates.size(), 0);
Assert.assertEquals(brands.size(), 0);
}
}
Error for reference:
Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity
constraint violation: "FKC4BCIKI2WSPO6WVGPO3XLA2Y9: PUBLIC.TAX_RATE
FOREIGN KEY(BRAND_ID) REFERENCES PUBLIC.BRAND(ID) (1)"; SQL statement:
delete from brand where id=? [23503-192]
UPDATE: modified my brandService.deleteAll() method to do the following:
#Override
public void deleteAll() {
Iterable<Brand> iter = this.brandRepository.findAll();
iter.forEach(brand -> this.brandRepository.delete(brand) );
}
Still does not work.
UPDATE 2: It only appears to be a problem via tests. Cascade seems to work okay with the app running.
I think you want to take a look at #OnDelete annotation which generates a DDL-level cascade delete.
This will add an ON DELETE CASCADE to the FOREIGN KEY definition if you're using the automatic schema generation (e.g. hbm2ddl). However, using Flyway is almost always a better choice than hbm2ddl.
Your mapping becomes:
#OneToMany(mappedBy = "brand", orphanRemoval = true, fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;

Resources