Spring JPA Bi-Directional #ManyToOne doesn't work - spring

we have linked 2 entities with bi-directional #ManyToOne relation like this:
#Entity
#Setter
#Getter
#Builder
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "foo")
private Set<Bar> bars;
}
#Entity
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Bar {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "foo_id")
private Foo foo;
}
When I create Foo entity and Bar entity, and try to set created Foo into Bar, change is not reflected on other side.
Here is code from my integration test:
Foo foo = fooRepository.save(Foo.builder()...build());
Bar bar = new Bar();
bar.setFoo(foo);
bar.set...();
barRepository.save(bar);
Foo updatedFoo = fooRepository.findById(foo.getId()).get();
List<Bar> bars = updatedFoo.getBars(); // this is null
I have no idea why bars from fetched foo is null. When I try fetch given Bar from repository I can see saved Foo on this side.

have you try adding cascade to your mappings,
#OneToMany(fetch = FetchType.LAZY, mappedBy = "foo",cascade= CascadeType.ALL)
private Set<Bar> bars;
#ManyToOne(cascade= CascadeType.ALL)
#JoinColumn(name = "foo_id")
private Foo foo;
and your code:
Foo foo = fooRepository.save(Foo.builder()...build());
Bar bar = new Bar();
bar.setFoo(foo);
bar.set...();
barRepository.save(bar);
barRepository.save(bar) will not persist the foo.
when you add cascade it will persist both the entities or you need to manually save the foo entity with fooRepository.

Short Answer
JPA require you to set both sides of a relationship
As long as you set the owning side bar.setFoo() which you did, the database will be updated with the relationship changes. So your database is correct
The non-owning side foo will only reflect what is in the database if you manually set it, or you force it to be refreshed or reloaded eg entityManager.refresh(foo)
Notes
In a transactional session, every call to repository does not translate to database call. Since hibernate all ready has foo as a managed entity in line1,
when you call, fooRepository.findById(foo.getId()).get(); JPA does not issue a select statement. If you search about Transactional write-behind and Repeatable Read, you can find out about this more
Foo foo = fooRepository.save(Foo.builder()...build()); - foo becomes managed entity, due to Repeatable read guarantee, JPA will return the same foo for all subsequent retrievals of foo.
It does not even go to database if you call fooRepository.findById(fooId) after the save.
barRepository.save(bar); - bar also becomes managed
Both foo and bar are managed entities, but foo says I don't have any bars and bar says I know a foo as you didn't set foo.getbars().add(bar). This is incorrect in memory as you have to explicitly set both side of relationships.
If you complete the current transactional method without adding the bar to foo, and then if you call fooRepository.findById(fooId) in another transactional, it will show you it get foo and bars.
More references
why-do-i-need-to-set-the-entities-both-ways-in-jpa
do-i-have-to-set-both-sides-for-a-bidirectional-relationship

Related

Spring MapsId not resolving target entity

I have such a case where I need to have internally many-to-one using hibernate proxies and only id externally, here using MapsId. The issue appears when I try to save something, because the target entity is not fetched, when I set the value only on the id.
Let's take an example: I have an Account table and DeviceConfig table. Inside the DeviceConfig's class definition, I add account in a many-to-one relation and accountId in relation with #MapsId.
Now when creating, I always set a value to accountId, but never the value is picked up, and the backend throws an SQL error, because the field cannot be null.
#Table(name = "djl_device_config")
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
public class DeviceConfig extends CoreEntity {
...
#JsonIgnore
#ManyToOne
#MapsId("accountId")
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "account_id", insertable = false, updatable = true, nullable = true)
private UUID accountId;
}
So I suppose this is a config error on my side, but I've been reading the JPA for these three days and I still don't know what's wrong or what I should do to achieve the behaviour I expect.
That for any help you'll provide.

Hibernate detached entity passed to persist error in Spring Boot in Kotlin using OneToOne with MapsId

I have the following entities in a fairly simple and straightforward Spring Boot application in Kotlin:
#Entity
class Target(
#Id #GeneratedValue var id: Long? = null,
// ... other stuff
)
#Entity
class Ruleset(
#OneToOne(fetch = FetchType.LAZY) #MapsId
var target: Target,
#Id #GeneratedValue var id: Long? = null,
// ... other stuff
)
And I have the following code to create them upon startup of a #Component:
#PostConstruct
#Transactional
fun init() {
val target = Target()
targetRepository.save(target)
val rule = Ruleset(target)
rulesetRepository.save(rule)
}
And when this runs I get the "detached entity passed to persist: com.mystuff.Target" error. I've used this approach in the past (see here: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/) without issue, although never in trying to create them at the same time in the same method. I've also tried using the entity passed back by the .save() call on the Target repository in the persist of the Ruleset object with no success.
I am able to fix this if I go back to the "normal" way of doing a OneToOne relationship:
#Entity
class Target(
#OneToOne(mappedBy = "target", cascade = [CascadeType.ALL],
fetch = FetchType.LAZY, optional = false)
var ruleset: Ruleset?
#Id #GeneratedValue var id: Long? = null
)
#Entity
class Ruleset(
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "target_id")
var target: Target,
#Id #GeneratedValue var id: Long? = null,
)
But this is annoying as it forces me to pass a null into the Target constructor and then update it immediately after creating the Ruleset. I can't figure out why the other, simpler approach doesn't work.

Put Reference from Audit table to Another Table in Hibernate Envers

I'm using Hibernate Envers for Auditing Change Data, I have a Class that store information about companies like this :
#Getter
#Setter
#Entity
#Table(name = "COMPNAY")
#Audited
public class Compnay {
private String name;
private String code;
}
and it's using Envers for keeping the changes of companies.
also, I have a class for Keep the data of items that manufacture in any of this company, the class will be like this :
#Getter
#Setter
#Entity
#Table(name = "COMPNAY")
#Audited
public class Item {
#Column(name = "NAME", nullable = false)
private String name ;
#Column(name = "CODE", nullable = false)
private String code;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COMPANY_ID", nullable = false)
private Compnay compnay;
}
Consider that there is a company in company table like this :
ID
NAME
CODE
1
Apple
100
2
IBM
200
and the data in the item's table will be like this :
ID
NAME
CODE
COMPANY_ID
3
iPhone
300
1
4
iPad
400
1
if I edit the information of Apple company and change the code from 100 to 300 how can I fetch the information of Items that were saved before this change with the previous code? Is there is any way to reference to audit table?
Yes, you can write a HQL query that refers to the audited entities. Usually, the audited entities are named like the original ones, with the suffix _AUD i.e. you could write a query similar to the following:
select c, i
from Company_AUD c
left join Item_AUD i on i.id.revision < c.id.revision
where c.originalId = :companyId

Reading #Transient fields from database

I have a spring boot application with database and entity with #Transcient field... here you have a sample code:
#Entity
#Table(name = "dogs")
#JsonInclude(Include.NON_NULL)
#ApiModel
public class Dog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Transient
public Boolean happy;
}
Dog dog1 = dogsRepository.findById(1);
dog1.setHappy(true);
Dog dog2 = dogsRepository.findById(1);
System.out.println("dog2 is happy = " + dog2.isHappy());
and the last line prints dog2 is happy = true on the screen. How it is possible? #Transient fields are not persisting in a database.
The method dogsRepository.findById checks the 1st level cache. If it cannot be found there it will be fetched from the database and stored in the 1st level cache. That is why the 2nd call to findById will not retrieve it from the database but from the cache instead. That is why dog1 and dog2 are the same object in your case.
That will not happen for example if you clear the cache between the findById calls or execute the calls in different transactions.

Hibernate5 OneToMany and ManyToOne mapping giving null on foreign key

Yet another thread like this. I fighting with this for 4 days.
annotation #Getter and #Setter are from lombok plugin
My Place class
#Entity
public class Place {
#Getter
#Setter
#OneToMany(mappedBy = "place", targetEntity = Tag.class, cascade = CascadeType.ALL)
private Set<Tag> tags;
//...
}
Tag class which should be many
#Entity
public class Tag {
#Getter
#Setter
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_place_id")
private Place place;
//...
}
I'm saving it like this
Tag tagOne = new Tag("tagOne");
Tag tagTwo = new Tag("tagTwo");
Set<Tag> tagSet = new HashSet<>();
tagSet.add(tagOne);
tagSet.add(tagTwo);
Place place = new Place();
place.setTags(tagSet);
placeService.save(place);
saving looks is the single line sessionFactory.getCurrentSession().persist(entity) on every case. Saving entity with #OneToOne mapping it works like a charm.
You have a bidirectional association. You're initializing only one side of the association, and that side is the inverse side (because it has the mappedBy attribute). Hibernate only cares about the owner side. So, for Hibernate, there is no association between the tags and the place.
Note that cascade=ALL on a ManyToXxx association doesn't make sense. If 100 tags are referencing the same place, and you delete one of these tags, you don't want to also delete the place. And even if you want to, that won't work, because 99 other tags still reference it, which will cause a referential integrity error.

Resources