Caching an entity with spring data jpa - java

I have defined roles in my database role_template.
#Entity
#Table(name = "role_template")
#Cacheable
public class Role {
#Id
private int id;
private String name;
#Transient
private final int identity = new Random().nextInt(1000000) + 1;
}
I have one role at this moment with id=1 and name="admin"
My entity User has a list of roles defined as follow
#Entity
#Table(name = "app_user")
public class User {
[...]
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_assign",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
}
Roles are joined to users with my association table
[Table `role_assign`]
int user_id
int role_id
My problem is predictable, #Cacheable does not work.
I tried with 2 users, they have the same Role template, but not the same instance. The transient variable identity isn't equals for the role of the two users.. My app configuration is good, I think I forgot something to make it working for #JoinTable

Is this the javax.persistence.Cacheable annotation? Because it should.
I think your understanding of how caching works with JPA is wrong and your observations is not sufficient to decide if caching takes place or not.
#Cacheable is about the 2nd level cache. If an entity is pulled from the cache it is instantiated from information stored in the cache, and not actually the same instance. The latter wouldn't work. Entities can always only be attached to a single session, but the 2nd level cache lives across sessions.
Two representations of an entity should be the same instance exactly if they belong to the same session.
In order to decide if the cache is used or not you have two good options:
Log the SQL statements issued against the database and see if the data for the entity is selected over and over again, or only once.
Log the cache interaction and see what is going on directly.
How you do that depends on the JPA provider you use. Here are instructions for Hibernate.

Related

Hibernate FK reference issue

I have an entity with reference to another one by FK, at the same time I have a field mapped on the same column to have access right to the identifier, let's say
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
Long id;
#JoinColumn(name = "author_id")
#ManyToOne(fetch = FetchType.LAZY)
Author author;
#Column(name = "author_id", insertable = false, updatable = false)
Long authorId;
}
so, for now on select via JPA repository (findById for instance) the field of "authorId" is always null, but in actual database it exists and object of "author" fills correctly. Tested in the transaction and outside - result is the same.
About app - it is spring boot 2.2.8 with spring data
Are there any ideas where I can be wrong?
*Update: found the reason - all the found entities are cached somehow, after detaching them from persistence context all data loads as expected. Seems it's clear, but still cant get where interactions with these entities appear, obviously not in my tx - it is pretty small and simple. Never thought that neighboring transactions can affect cache this way =((

Hibernate Second level cache doesn't work for OneToOne associations

I am trying to enable Hibernate's 2nd level cache but cannot avoid multiple queries being issued for OneToOne relations.
My models are:
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Business {
#OneToOne(mappedBy = "business", cascade = {CascadeType.REMOVE}, fetch = FetchType.EAGER)
private Address address;
}
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Address {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "business_id", unique = true, nullable = false, foreignKey = #ForeignKey(name = "fk_business_id"))
private Business business;
}
When I run session.get(Business.class, id) with the Business with id id in the cache, no query is issued for loading Business but it does for Address.
I understand that Address is the relation owner and that in the Business cache entry there's no Address.id information, but wouldn't it be possible to solve this problem by applying the same mechanism as *ToMany relations does, creating a new cache region for each field? Assuming Business 1 is related to Address 2, there would be the following regions and entries in my cache after a first load:
Business
Business#1 -> [business model]
Business.address
Business.address#1 -> [2]
Address
Address#2 -> [address model]
I have tried to make it work by annotating Address.business with #NaturalId and the Address class with #NaturalIdCache. The cache region is created and populated but session.get(Business.class, id) does not use it.
My Business model has many more OneToOne relations whose foreign key is on the other side (not the Business) and we must list several at a time so the database server has to process dozens of queries per HTTP request.
I have read the Hibernate's User Guide, Vlad Mihalcea's explanation on 2LC and its in-memory dehydrated format, Baeldung's explanation and several other StackOverflow answers and cannot find a way to solve this.

OneToMany prevent checking for proxy object

I am trying to improve the performance of a repository-method. I have a OneToMany-relationship in one of my entities, UserEntity, with a set of AddressEntities that are loaded lazily.
In AddressEntity:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = EntityConstants.COLUMN_USER_ID, referencedColumnName = EntityConstants.COLUMN_USER_ID)
private UserEntity user;
In UserEntity:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<AddressEntity> addresses;
The problem is that when I fetch AddressEntity, a query is made to the database to the user table as well. From what I understand this is to check for the existence of the UserEntity, and to create a proxy object to it.
This takes time, and I am not interested in whether or not the user entity exists in this case. Is there any way to prevent hibernate to do this extra query and simply leave userEntity to null?
Thanks in advance,
Markus
I solved this by only selecting the attributes that I needed:
SELECT ad.longitude, ad.latitude FROM AddressEntity ad
This does not cause hibernate to check for proxy objects on relations that are not selected.

JPA cascade on entities from multiple persistenceUnits

I have an unidirectional relation between two entities:
*#Entity
public class XXX{
#Id
private Long Id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "YYY_ID")
private YYY yyy;
}
#Entity
public class YYY{
#Id
private Long Id;
private String someName;
}*
The entities belong to different schemas in the same database (but i may not use synonims, or give grants to schema...), so i have two persistence units:
1.persistenceUnit(xxx) - entity xxx is mapped there
2.persistenceUnit(yyy) - entity yyy is mapped there
Can i make jpa to auto perform cascade on the other persistence unit?
Example:
#PersistenceContext(unitName = "xxx")
private EntityManager em;
XXX xxx = new XXX();
YYY yyy = new YYY();
yyy.setSomeName("just some name"):
xxx.setYYY(yyy);
em.persist(xxx);
This should create two objects...
Is it possible? Help appreciated. Im using JPA2, Hibernate 4 on Jboss7
The JPA specification does not mandate that mappings across multiple persistent units be supported.
You can however, depending on your database permissions, use one persistence unit and use the #Table annotation to specify the scheme/catalog for any Entities not in the default schema.
#Entity
#Table(schema="xyz")
public class XXX{
#Id
private Long Id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "YYY_ID")
private YYY yyy;
}
Otherwise you would need to look for a solution at the database level or consider using a provider specific solution: I am not aware of anything for Hibernate but see, for example, the following EclipseLink functionality:
https://wiki.eclipse.org/EclipseLink/Examples/JPA/Composite
Starting with EclipseLink 2.3.0 JPA developers can now combine
persistence units together at runtime allowing entities to be stored
in different databases. This includes support for relationships
between entities in different persistence units (references across
databases)

Adding an entity into an large Many-To-Many relationship in JPA

I have a Group entity that has a list of User entities in a many to many relationship. It is mapped by a typical join table containing the two IDs. This list may be very large, a million or more users in a group.
I need to add a new user to the group, typically that will be something like
group.getUsers().add(user);
user.getGroups().add(group);
em.merge(group);
em.merge(user);
If I understand typical JPA operation, will this require pulling down the entire list of 1 million+ users into the collection in order to add the new user and then save? That doesn't sound very scalable to me.
Should I simply not be defining this relationship in JPA? Should I be manipulating the join table entries directly in a case like this?
Please forgive the loose syntax, I'm actually using Spring Data JPA so I don't directly use the entity manager directly very often, but the question seems to be general to JPA so I wanted to pose it that way.
Design your models like this and play with UserGroup for associations.
#Entity
public class User {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user",fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
#Entity
#Table(name="user_group",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "group_id"})})
public class UserGroup {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#ForeignKey(name = "usergroup_user_fkey")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "group_id", nullable = false)
#ForeignKey(name = "usergroup_group_fkey")
private Group group;
}
#Entity
public class Group {
#OneToMany(cascade = CascadeType.ALL, mappedBy="group", fetch = FetchType.LAZY )
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
Do like this.
User user = findUserId(id); //All groups wont be loaded they are marked lazy
Group group = findGroupId(id); //All users wont be loaded they are marked lazy
UserGroup userGroup = new UserGroup();
userGroup.setUser(user);
userGroup.setGroup(group);
em.save(userGroup);
Using the ManyToMany mapping effectively is caching the collection in the entity, so you might not want to do this for large collections, as displaying it or passing the entity around with it triggered will kill performance.
Instead you might remove the mapping on both sides, and create an entity for the relation table that you can use in queries when you do need to access the relationship. Using an intermediate entity will allow you to use paging and cursors, so that you can limit the data that might be brought back into usable chunks, and you can insert a new entity to represent new relationships with ease.
EclipseLink's attribute change tracking though does allow adding to collections without the need to trigger the relationship, as well as other performance enhancements. This is enabled with weaving and available on collection types that do not maintain order.
The collection classes returned by getUsers() and getGroups() don't have to have their contents resident in memory, and if you have lazy fetching turned on, as I assume you do for such a large relationship, the persistence provider should be smart enough to recognize that you're not trying to read the contents but just adding a value. (Similarly, calling size() on the collection will typically cause a SQL COUNT query rather than actually loading and counting the elements.)

Categories