Spring JPA - One to one but keep history - spring-data-jpa

I some classes classes that were used to create an object like this:
{
name: "Foo Inc",
...
accountingConnection: {
id: 123,
token: "abc",
createdAt: <some_date>,
disconnectedAt: null
}
}
Here is what I have for the JPA files:
Business.java:
...
#JsonManagedReference
#OneToOne(mappedBy = "business", cascade = CascadeType.ALL)
private AccountingConnection accountingConnection;
...
AccountingConnection.java
...
#JsonBackReference
#OneToOne()
#JoinColumn(
name = "business_id",
referencedColumnName = "id",
nullable = false
)
private Business business;
...
Now I need to change this to keep a history of the connections, I would like to keep all the connections in a table, but when calling the business object, only return the one without a disconnect date, and work with that normally, is that possible without manually looking up each, or looping through a OneToMany array / picking one without having multiple definitions for the business dto?

You can simply have a List< AccountingConnection> oldConnectionattribute that you update whenever theaccountingConnection` changes.

Related

Spring JPA/Hibernate Repository findAll is doing N+1 requests instead of a JOIN by default in Kotlin

I am working in a Spring JPA/Hibernate application with Kotlin and I want to find all elements in an entity.
That entity has a foreign key with a #ManyToOne relationship. I want to get all elements with their associated values with a JOIN query avoiding the N+1 problem.
One thing is that the foreign keys are not related to the primary keys, but to another unique field in the entities (UUID).
I was able to make that query with a JOIN creating a custom Query with a JOIN FETCH, but my point is to avoid creating those queries and make those JOINS in all findAlls by default.
Is that possible or do I have to make a query in JPQL manually to force the JOIN FETCH?
Here is the example code:
#Entity
data class A {
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
}
#Entity
data class B {
#Id
val id: Long,
...
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
}
#Repository
interface Repo<B> : CrudRepository<B, Long>
...
repo.findAll() // <-- This triggers N+1 queries instead of making a JOIN
...
Another option for you is using EntityGraph. It allows defining a template by grouping the related persistence fields which we want to retrieve and lets us choose the graph type at runtime.
This is an example code that is made by modifying your code.
#Entity
data class A (
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
) : Serializable
#NamedEntityGraph(
name = "b_with_all_associations",
includeAllAttributes = true
)
#Entity
data class B (
#Id
val id: Long,
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid")
val a: A
)
#Repository
interface ARepo: CrudRepository<A, Long>
#Repository
interface BRepo: CrudRepository<B, Long> {
#EntityGraph(value = "b_with_all_associations", type = EntityGraph.EntityGraphType.FETCH)
override fun findAll(): List<B>
}
#Service
class Main(
private val aRepo: ARepo,
private val bRepo: BRepo
) : CommandLineRunner {
override fun run(vararg args: String?) {
(1..3L).forEach {
val a = aRepo.save(A(id = it, uuid = UUID.randomUUID(), name = "Name-$it"))
bRepo.save(B(id = it + 100, a = a))
}
println("===============================================")
println("===============================================")
println("===============================================")
println("===============================================")
bRepo.findAll()
}
}
On B entity, an entity graph named "b_with_all_associations" is defined, and it is applied to the findAll method of the repository of B entity with LOAD type.
These things will prevent your N+1 problem by fetching with join.
Here is the SQL log for the bRepo.findAll().
select
b0_.id as id1_1_0_,
a1_.id as id1_0_1_,
b0_.a_uuid as a_uuid2_1_0_,
a1_.name as name2_0_1_,
a1_.uuid as uuid3_0_1_
from
b b0_
left outer join
a a1_
on b0_.a_uuid=a1_.uuid
ps1. due to this issue, I don't recommend using many to one relationship with non-pk. It forces us to use java.io.Serializable to 'One' entity.
ps2. EntityGraph can be a good answer to your question when you want to solve the N+1 problem with Join. But I would recommend the better solution: try to solve it with Lazy loading.
ps3. It's not a good idea that using non-pk associations for Hibernate. I truly agree on this comment. I think it's a bug that is not solved yet. It breaks the lazy loading mechanism of hibernate.
As far as I know, the fetch mode only applies to EntityManager.find related queries or when doing lazy loading but never when executing HQL queries, which is what is happening behind the scenes. If you want this to be join fetched, you will have to use an entity graph, which is IMO also better as you can define it per use-site, rather than globally.
I don't know how to configure exactly what you are asking, but the following suggestion might be worth considering...
Change
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
to
#ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
And then on your entity A, add the annotation to the class
#BatchSize(size = 1000)
Or whatever batch-size you feel to be appropriate.
This will generally give you the results in 2 queries if you have less than 1000 results. It will load a proxy for A rather than joining to A, but then the first time that A is accessed, it will populate the proxies for BATCH_SIZE number of entities.
It reduces the number of queries from
N + 1
to
1 + round_up(N / BATCH_SIZE)
The findAll implementation will always load b first and then resolve it's dependencies checking the annotations. If you want to avoid the N+1 problem you can add the #Query annotation with JPQL query:
...
#Query("select b from TableB b left join fetch b.a")
repo.findAll()
...

Undirectional many to one JPQL select foreign keys

I read different posts, but I couldnt find a solution that works. I think the OneToMany relation causes the problem. I have to following entities:
// Mandant.class (client)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "FK_MANDANT")
private List<Leistung> leistungen = new ArrayList<>();
// Mitarbeiter.class (employee)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "FK_MITARBEITER")
private List<Leistung> leistungen = new ArrayList<>();
// Kategorie.class (category)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "FK_KATEGORIE")
private List<Leistung> leistungen = new ArrayList<>();
which have undirectional OnetoMany relations. I wrote a method filterLeistungen(...) in the LeistungsRepository.class. Now I want to filter the payments (Leistungen) by vague amount of filters. So I compound the filters with a Stringbuilder to a JPQL-Select.
sb.append("l.mandant = ");
sb.append(mandant.getId());
sb.append(" AND ");
Now I create a query with createQuery().
EntityManager entityManager = PREntityManagerFactory.getInstance().createEntityManager();
RepositoryUtils<List<Leistung>> repositoryUtils = new RepositoryUtils<>();
JpaFunction<List<Leistung>> function = () ->
entityManager.createQuery(sb.toString(), Leistung.class).getResultList();
return repositoryUtils.withoutTransaction(entityManager, function);
But when I execute the query I get the following exception:
Exception in thread "main" java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [SELECT l FROM Leistung l WHERE l.fakturierungsdatum IS NULL AND l.mandant = 1 ORDER BY l.datum].
[64, 73] The state field path 'l.mandant' cannot be resolved to a valid type.
I also tried l.mandant.id or l.fk_mandant.id or l.fk_mandant. I use EclipseLink and a MySQL database. All entities have a primary key called id. Is it possible to filter payments (Leistungen) by foreign keys? The payment doesn't know the other entities, because of the undirectional relation. But on the database, the payment has the foreign key attributes, so it should be possible?
I got a tip in a german forum. With the createNativeQuery() method I'm able to use the database dialect, here MySQL. But I think this isn't a really nice solution, because there is no guarantee for platform independence.

jpa: update relationship for a new parent-entity

i have two entities Price<-1----1->PriceDetail mapped as OneToOne.
how can i handle different scenarios for this relation. so i have cases where i always want a new price and a new pricedetail,
but i also would be able to create a new price only and update the pricedetail (with data from a previous price-entity).
my current solution is to remove the pricedetail-entity, how can it be done with updating the pricedetail-entity?
#Entity
class Price {
#OneToOne(cascade=CascadeType.ALL,mappedBy = "price")
private PriceDetail priceDetail;
}
#Entity
class PriceDetail {
#OneToOne
private Price price;
}
save-method:
EntityManage em = getEntityManager();
for (Price price : getAllPrices()){
Price oldPrice = Price.getById(price.getId());
if (!oldPrice.equals(price)){ //if we have price-changes
if (PriceCatalog.entryExists(oldPrice)){ //if the current-price is in a catalog
//current solution: remove entry from PriceDetail, but i want to update PriceDetail-Entity, pointing
//to the newly created price
em.remove(oldPrice.getPriceDetail());
em.commitTransaction();
oldPrice.setActive(false); //referenced price in PriceCatalog is now inactive
//sets id null, so that a new price-entity is created
price.setId(null);
price.setActive(true);
em.persist(price); //also inserts a new price-detail
}else {
em.merge(price);
}
}
}
em.commitTransaction();
because of CascadeType.ALL-Annotation in Price-Entity, JPA tries to insert a new PriceDetail-Entity.
approach 1:
price.getPriceDetail().setId(oldPrice.getPriceDetail().getId());
-> Error: insert into pricedetail violates unique-constraint: Key already exists
approach 2:
//ommit cascade
#OneToOne(mappedBy = "price")
protected PriceDetail priceDetail;
then approach 1 works, but creating a complete new price results in:
During synchronization a new object was found through a relationship that was not marked cascade PERSIST
approach 2 is not an option in you case, this is the correct mapping to do a bidirectional one-to-one association:
//you must do this to handle the bidirectional association
#OneToOne(mappedBy = "price")
protected PriceDetail priceDetail;
Now the problem is :price is a new entity then the entityManager will call persit operation on price.getpriceDetail() because cascade persist is triggered automatically (not cascade-merge) to avoid this strange behaviour you can do the following.
EntityManage em = getEntityManager();
for (Price price : getAllPrices()){
Price oldPrice = Price.getById(price.getId());
if (!oldPrice.equals(price)){ //if we have price-changes
if (PriceCatalog.entryExists(oldPrice)){ //if the current-price is in a catalog
//current solution: remove entry from PriceDetail, but i want to update PriceDetail-Entity, pointing
//to the newly created price
//em.remove(oldPrice.getPriceDetail());
//em.commitTransaction();
oldPrice.setActive(false); //referenced price in PriceCatalog is now inactive
PriceDetail priceDetailold = price.getPriceDetail();
price.setPriceDetail(null);
priceDetailold.setPrice(null);
//sets id null, so that a new price-entity is created
price.setId(null);
price.setActive(true);
em.persist(price); //inserts a new price
price.setPriceDetail(priceDetailold);
em.merge(price);// attach the pricedetail to the price
}else {
em.merge(price);
}
}
}
em.commitTransaction();

How to successfully do delete/remove operation in ManyToMany relationship?

I'm using ManyToMany with JPA annotation, I need your valuable suggestions.
(Assume Person and Address. Same Address is referred to more person (living at same address)). I have to delete a person from that address.
Person p1 = new Person();
Person p2 = new Person();
Address add1 = new Address();
p1.add(add1);
p2.add(add1);
As well doing
add1.add(p1) ;
add1.add(p2) ;
THen on merge or persist iit mapped appropriately.
p1 - add1
p2 - add1
I have to delete p2 alone , when i did
p2.removeAddress(add1)
removeAddress(add1) {
addColelction.remove(add1) }
What happens is it deleted the entry for address and again by Hibernate jpa provider again tries to persist at Address entity and says "deleted entity passed to persist " and henc transaction roll back happens.
My correction on the question. The mapping exist as
In Script side :
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name = "XXXX", joinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) })
#org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Collection<Parser> parsers;
In Parser side
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "parsers")
private Collection<Script> scripts;
The data saved as
Script1 - Parser1
Script2 - Parser1
Our data model is Object A has oneTomany to B , B has oneTomany to Script objects.
Say collection of A has (B1,B2,.....)
B1 has (Script1)
B2 has (Script2)
When we want to delete that B2 object (we do just EM.merge(A)), we want the particular B2 from the collection has to be deleted and the related the Script2 has to be deleted. Script2 delete should remove the intermediate entry alone but should not delete the Parser.
But Parser1 gets deleted and Transaction gets rolled back saying ''deleted entity passed to persist
Please share your ideas.
You mention you only want the join table cleared up, but you have both DELETE_ORPHAN and cascade all set on the Script->Parser mapping. The first setting seems to be a hibernate option equivalent to JPA's orphan removal, which will cause any entities de-referenced from the collection to be deleted in the database. This will cause Address1 in the example to be deleted.
The cascade all option will force the remove operation to be cascaded to any entities still referenced by Person/Script when remove is called on Person/Script. In the first example this will cause Address2 to be removed/deleted from the database.
If you want address 1 or address2 to survive, then remove the appropriate setting. As mentioned in comments, you will also want to clean up your references, as the survivors will be left referencing a deleted Person/Script entity which may cause problems in your application.

JPA #Jointable does not create column for id

I'm working on JBoss AS 7 using JPA to have a List of Beans in a Entity-Bean like this:
#Entity
class section {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private List<Component> components;
// ...
The table to join the two tables gets created, but it does not contain an Id, which leads to JPA creating a unique-constrain on one of the columns (SECTION_ID). Which is not really what I want, because one section can have more than one component. One component can be used in more than one section too.
I already tried
#JoinTable(name="SECTION_COMPONENT",
joinColumns = {
#JoinColumn(name="section_id", unique = false)
},
inverseJoinColumns =
#JoinColumn(name="component", unique = false)
}
I guess JPA needs at least one unique column, so it just adds that to the last column if nothing else is specified. I'd be fine with adding a new column "id" to set up a primary (or unique) key. But I am not sure how to do that.
Thanks a lot for any help
The mapping is not correct: #ManyToOne in your case means that you have one component that has many sections:
#ManyToOne
private Component component;
According to your description, you need an #ManyToMany relationship:
#ManyToMany
private List<Component> components;