understanding hibernate cache - java

If I have this method in object class:
#OneToMany( fetch = FetchType.EAGER,
cascade = { CascadeType.ALL },
mappedBy = "object" )
#org.hibernate.annotations.Cascade(
{org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#Column( nullable = false )
public Set<ObjectEntry> getObjectEntries() {
return this.objectEntries;
}
and I put #cache both on ObjectEntry and on Object
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Object extends HashCodeValidator {
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ObjectEntry extends HashCodeValidator
Do I still need to put #cache on getObjectEntries like this:
#org.hibernate.annotations.Cascade(
{org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#Column( nullable = false )
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<ObjectEntry> getObjectEntries() {
return this.objectEntries;
}
Do I need to define cache for each query if I specifically add
hibernate.cache.use_query_cache = true
?

(...) Do I still need to put #cache on getObjectEntries like this:
Yes, you still have to cache a collection if you want to (that will be cached in a specific cache region).
Do I need to define cache for each query if I specifically add hibernate.cache.use_query_cache = true
From the reference documentation about the hibernate.cache.use_query_cache property (section 3.4. Optional configuration properties):
Enables the query cache. Individual queries still have to be set cachable. e.g. true|false
So, yes, you still have to set a query cachable (by calling setCacheable(true) on a query or criteria object) if you want to - which is IMO a good thing.

Related

Implementing soft delete with #Filter and lazy loaded associations

I am implementing soft delete with Hibernate. Each entity that supports soft-deleting has an attribute for it.
#Column(name = "DELETED")
private boolean deleted;
I have created #FilterDef in package-info.java for package with domain objects.
#FilterDef(name = "deletedFilter",
parameters = #ParamDef(name = "includeDeleted", type = Boolean.class),
defaultCondition = ":includeDeleted = true OR DELETED = false"
)
applied it to all DeleteAware entities
#Filter(name = "deletedFilter")
public class CustomerGroup
and enabled in when using in queries
Session session = em.unwrap(Session.class);
session.enableFilter("deletedFilter")
.setParameter("includeDeleted", fp.isDeleted());
Filter is applied and works correctly for primary entity (for example when I query customers I can see that additional where condition is always applied as needed).
Problem is with filter of association. Let's say Customer entity has collection of CustomerGroup.
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinTable(name = "CUSTOMER_CUSTOMER_GROUP",
joinColumns = #JoinColumn(name = "CUSTOMER_ID"),
inverseJoinColumns = #JoinColumn(name = "CUSTOMER_GROUP_ID"))
private Set<CustomerGroup> groups;
However when I query for Customer, groups collection contains deleted entities. I have turned on sql logging and I can see that condition is not applied for lazy query. However if I change
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
to
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
it works.
Both entities are annotated with #Filter. I have also tried applying #Filter annotation to collection itself success without. For initial testing I have also ensured that filters are not disabled and includeDeleted parameter is always false.
#Where annotation on entities works like a charm, but cannot be disabled (99% percent of queries we want to filter out deleted objects but there is that pesky 1% where we need deleted ones).
We are using Hibernate 6.1.13 provided in WildFly 27 application server. Looks like filters are not applied when relation is lazy loaded. Am I missing something?

Hibernate map an association as a map

I have a MessengerData class which contains a list of resources. This my object MessengerData:
"messengerData":{
"fr":[
{
"messengerType":"ImageCategoryTitle",
"imageURL":"https://assets.pernod-ricard.com/uk/media_images/test.jpg"
}
"EN":[
{
"messengerType":"ImageCategoryTitle",
"imageURL":"https://assets.pernod-ricard.com/uk/media_images/test.jpg",
}
]
This is how I define my object MessengerData:
#Entity
public class MessengerData
{
#Basic
#Id
#GeneratedValue(generator = "notification-system-uuid")
#GenericGenerator(name = "notification-system-uuid", strategy = "uuid")
private String messengerDataId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) /* , mappedBy = "idResource" */
#JoinTable(name = HemisTablesNames.MESSENGER_RESOURCES, joinColumns = #JoinColumn(name = "idResource"),
inverseJoinColumns = #JoinColumn(name = "messengerDataId"))
private Map<String, Set<Resource>> resources;
}
But I am getting this exception: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.ubiant.hemis.type.MessengerData.resources[java.util.Set]
Could someone help me with this ?
Hibernate doesn't seem to support multimaps (that's what resources is) directly but you could provide your own custom type like described here: https://xebia.com/blog/mapping-multimaps-with-hibernate/ .
However, since your data seems to be Json anyway you could go one more step and directly map the resources as json, i.e. into a text column (or a json column if the db supports it): http://fabriziofortino.github.io/articles/hibernate-json-usertype/
We're doing something similar, which on an outline looks like this (this is a generic type, in most cases a more specific POJO will be better):
class JsonData extends HashMap<String, Object> { ... }
//JsonbUserType is a custom implementation based on code like the one linked above
class JsonDataUT extends JsonbUserType<JsonData > { ... }
Then in package-info.java of the package the user type is in we have this:
#TypeDefs ( {
#TypeDef ( name = "JsonDataUT ", typeClass = JsonDataUT.class, defaultForType = JsonData.class ),
...
})
package our.package;
And our entities then just contain this:
#Column( name = "data_column")
private JsonData data;
One advantage of this is that we don't have to bother with more complex mappings, especially if types are dynamic.
One (major) disadvantage, however, is that you can't use that property in query conditions since Hibernate wouldn't know how to filter in a json column (we're using Postgres so it would really be a jsonb typed column, hence the usertype name) and afaik there's not reasonable way to provide custom functions etc. to enable things like where data.someFlag is true in HQL.

Hibernate ignores 'lazy' fetch type and loads properties immediately

I use Hibernate 5.2.5 (also use kotlin and spring 4.3.5 if that matters) and I want some of the fields of my class to be loaded lazily. But the issue is that all fields are loaded immediately, I don't have any special Hibernate settings neither use Hibernate.initialize().
#Entity(name = "task")
#Table(name = "tasks")
#NamedQueries(
NamedQuery(name = "task.findById", query = "SELECT t FROM task AS t WHERE t.id = :id")
)
class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id", nullable = false)
lateinit var author: User
#OneToOne(fetch = FetchType.LAZY, mappedBy = "task")
var edit: TaskEdit? = null
}
This is how I query
TaskRepoImpl:
override fun findById(id: Int): Task? {
val task = getCurrentSession().createNamedQuery("task.findById", Task::class.java)
.setParameter("id", id)
.uniqueResult()
return task
}
TaskService:
#Transactional
fun find(id: Int): Task? {
return taskRepo.findById(id)
}
And the output:
Hibernate: select task0_.id as id1_1_, task0_.author_id as author_i3_1_ from tasks task0_ where task0_.id=?
Hibernate: select user0_.id as id1_3_0_, user0_.enabled as enabled2_3_0_, user0_.name as name3_3_0_, user0_.password as password4_3_0_ from users user0_ where user0_.id=?
Hibernate: select taskedit0_.id as id1_0_0_, taskedit0_.task_id as task_id3_0_0_, taskedit0_.text as text2_0_0_ from task_edits taskedit0_ where taskedit0_.task_id=?
Please advice what's wrong with my code and how to make Hibernate load properties lazily? Thank you!
Hibernate cannot proxy your own object.
There are at least three well known solutions for this problem:
The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.
The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add #LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.
The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case #LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.
#Entity
public class Animal implements FieldHandled {
private Person owner;
private FieldHandler fieldHandler;
#OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
#LazyToOne(LazyToOneOption.NO_PROXY)
public Person getOwner() {
if (fieldHandler != null) {
return (Person) fieldHandler.readObject(this, "owner", owner);
}
return owner;
}
public void setOwner(Person owner) {
if (fieldHandler != null) {
this.owner = fieldHandler.writeObject(this, "owner", this.owner, owner);
return;
}
this.owner = owner;
}
public FieldHandler getFieldHandler() {
return fieldHandler;
}
public void setFieldHandler(FieldHandler fieldHandler) {
this.fieldHandler = fieldHandler;
}
}
Can you try this:
http://justonjava.blogspot.in/2010/09/lazy-one-to-one-and-one-to-many.html
reference:
Why Lazy loading not working in one to one association?

Cache inconsistency - Entity not always persisted in cached Collection

I'm having an issue where a Validation instance is added to a Collection on a Step instance.
Declaration is as follows:
Step class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Step extends AbstractEntity implements ValidatableStep {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "step_id", nullable = false)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();
#Override
public void addValidation(Validation validation) {
// do some stuff
...
// add validation instance to collection
getValidations().add(validation);
}
}
Validation class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Validation extends AbstractEntity {
//some properties
}
Both classes are Cacheable with a READ_WRITE strategy applied. The unidirectional Collection of Validations are also cached with same strategy.
One would expect when a read-write transaction that invokes addValidation(new Validation('userName')); commits, the new Validation would be visible in a subsequent read-only transaction. The weird thing is that sometimes it does work and sometimes it doesn't work...
The first transaction always succeeds; we see the new validation being persisted in database and Step's version property (for optimistic locking puposes) getting incremented. But sometimes, the 2nd read transaction contains a Step instance with an empty Validation Collection...
Our Hibernate caching config is as follows:
hibernate.cache.use_second_level_cache = true
hibernate.cache.use_query_cache = true
hibernate.cache.region.factory_class = org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
hibernate.cache.provider_configuration_file_resource_path = classpath:ehcache.xml
net.sf.ehcache.hibernate.cache_lock_timeout = 10000
Any idea what's causing this weird (and random) behavior?
The Hibernate Collection Cache always invalidates existing entries and both the Entity and the Collection caches are sharing the same AbstractReadWriteEhcacheAccessStrategy, so a soft-lock is acquired when updating data.
Because you are using a unidirectional one-to-many association, you will end up with a Validation table and a Step_validation link table too. Whenever you add/remove a Validation you have to hit two tables and that's less efficient.
I suggest you adding the #ManyToOne side in the Validation entity and turn the #OneToMany side into a mapped-by collection:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "step")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();

Referential integrity with One to One using hibernate

I'm having two tables -
Foo { foo_id, name }
Foo_properties { fp_id, foo_id, phoneNumber}
Now I want to map this in my object model using hibernate..
I need a foo_id in Foo_properties because i want to maintain referential integrity and want to add ON DELETE CASCADE constraint.
so I mapped the relation in the following way -
#Entity
public class Foo{
#Id
private long foo_id;
private String name;
#OneToOne(mappedBy = "foo")
private FooProperties fooProperties;
}
#Entity
public class FooProperties{
#Id
private long fp_id;
private String phoneNumber;
#OneToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
}
Now since the owning side is FooProperties class, I'm facing following issues -
If I set the new instance of FooProperties to Foo the existing FooProperties still remains in DB and hibernate doesn't delete that instance, e.g.
Foo foo = entityManager.find(Foo.class, fooId);
foo.setFooProperties(new FooProperties("xxx-xxx-xxx"));
entityManager.merge(foo);
This results into the new row in FooProperties table along with the existing one. Now I don't understand how I can change my mapping to so I can have above code (or variant of it) working for all scenarios, that means I need Foo as a owning side and foo_id in FooProperties. Is there any way to define the mapping like this?
NOTE: I already asked question based on this but I think I wasn't clear in previous question so asked this another one.
You were already told to use orphanRemoval = true or CascadeType.DELETE_ORPHAN. However, due to casuistics in interpretation of JPA Specification it wouldn't work as expected for one-to-one relationships (HHH-5559).
You can achieve a proper behaviour of orphanRemoval with the following trick:
#Entity
public class Foo{
#OneToMany(mappedBy = "foo", orphanRemoval = true)
private List<FooProperties> fooProperties;
public FooProperties getFooProperties() {
if (fooProperties == null || fooProperties.isEmpty()) return null;
else return fooProperties.get(0);
}
public void setFooProperties(FooProperties newFooProperties) {
if (fooProperties == null) fooProperties = new ArrayList<FooProperties>();
else fooProperties.clear();
if (newFooProperties != null)
fooProperties.add(newFooProperties);
}
...
}
#Entity
public class FooProperties{
#ManyToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
...
}
Or even this, if you don't need FooPropeties.foo:
#Entity
public class Foo{
#OneToMany(orphanRemoval = true)
#JoinColumn(name = "foo_id", nullable = false)
private List<FooProperties> fooProperties;
// getter/setter as above
...
}
Bar is the owner of the association (as indicated by the mappedBy on the inverse side) and thus the cascade has to be set there.
Edit:
To invert that, this might help.
There are 2 options for you to choose from, since you don't want to change your mapping :
Do it via your service layer logic. I think you have a similar question already.
Use the Hibernate annotation #Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) on the Foo side of the relationship. However this is explicitly Hibenate and JPA 2 doesn't include support for the same.
I think instead of calling merge on the entity, if you directly call update on session object then hibernate will first delete the existing row and then it will add the new one. I implemented the same, but, in my case I used xml for mapping the entity. I hope this will help you.

Categories

Resources