Hibernate upgrade from 3 to 4: Criteria transaction ceases to work - java

I have recently upgraded Spring 2.5 to 3.2 and Hibernate 3 to 4.2.8 in a general upgrade of a web application. Most things are working now, but there is one Criteria transaction that is not working and has me puzzled. The new version returns no result (but no errors), while the old one retrieved properly the requested value.
The code is the same one in the old and new versions, and I have verified that the argument that reaches it is the same. Here is the Java code:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(ViewingResource.class);
criteria.createCriteria("viewings","currentViewings");
criteria.add(Property.forName("currentViewings.id").eq(viewingId));
ViewingResource result = (ViewingResource)criteria.uniqueResult();
ViewingResource is my entity, which is defined as follows:
#Entity
#DiscriminatorValue("viewing")
public class ViewingResource extends AbstractInformationResource {
private static final long serialVersionUID = -4569093742552159052L;
#OneToOne(targetEntity = Attribute.class, fetch = FetchType.LAZY)
#JoinColumn
private Attribute primaryAttribute;
#OneToMany(targetEntity = Viewing.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval=true)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "informationresource_viewings")
#OrderBy("sort")
private Set<ResourceViewing> viewings;
public Set<ResourceViewing> getViewings() {
return viewings;
}
public Attribute getPrimaryAttribute() {
return primaryAttribute;
}
}
As for the abstract class it extends:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "type",
discriminatorType = DiscriminatorType.STRING
)
#Table(name = "informationresource")
abstract public class AbstractInformationResource extends PersistentEntity<String> {
private static final long serialVersionUID = 8709376067232042462L;
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private int sort;
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getSort() {
return sort;
}
}
And the original PersistentEntity is just an extension of Serializable with an id and no annotations.
I enabled Hibernate logs and found the problem may be in the way annotations work between in Hibernate 3 and 4, for the Hibernate generated SQL strings differ in this way:
Hibernate 3:
select
... (maps to all columns)
from
informationresource this_
inner join
informationresource_viewings viewings3_
on this_.id=viewings3_.informationresource_id
inner join
Viewing currentvie1_
on viewings3_.viewings_id=currentvie1_.id
where
this_.type in (
'viewing', 'directory'
)
and currentvie1_.id=?
Whereas in Hibernate 4, the generated SQL performs no joins:
select
... (maps to all columns, except type, attributeType and fieldName)
from
informationresource this_,
informationresource_viewings viewings3_,
Viewing currentvie1_
where
this_.id=viewings3_.informationresource_id
and viewings3_.viewings_id=currentvie1_.id
and this_.type='viewing'
and currentvie1_.id=?
Any hints that may help me advance with this issue? My current guess is that maybe I skipped some annotation definition that has been changed or modified since Hibernate 3, but so far I haven't been able to find anything illegal in the way I declare them - and my attempts to modify the #Join have been unsuccessful so far.
EDIT. After toying with this for some time, I have found that the issue may be related to the #DiscriminatorColumn of the abstract class. I have found that the problem lies that my type for this kind of request is never 'viewing', but 'directory'. In the old generated SQL I had both types generated:
this_.type in (
'viewing', 'directory'
)
But in the new sql this is constrained to 'viewing':
and this_.type='viewing'
I have changed in the new SQL this line, and it returns the right values that I need. The column type has only those two values, 'viewing' and 'directory'. So my question now is how to make Criteria to keep asking for the types there instead of forcing 'viewing' type.

Finally I found the solution, thanks to the hint I appointed in the EDIT block.
The solution came from establishing a formula in the base class:
#DiscriminatorFormula("case when type in ('viewing','directory') then 1 else 2 end")
And then changing in viewing resource the discriminator value annotation:
#DiscriminatorValue("1")
I really don't know why in Hibernate 3 I got all the values in the discrimination, and in Hibernate 4 the value was only this one, since the code had not changed at all. So if anyone in the future sees some similar behavior, maybe this trick can help you.

Related

CrudRepository save() throws "Row was updated or deleted by another transaction"

I have an error during an update transaction on my database using JpaRepository. I saw that there are many questions about this same error here but none of the solutions mentioned seem to solve my case. This is my main method:
public View update(Long productCode, Long customerCode, UpdateForm form) {
var entityToUpdate = repository.findByProductCodeAndCustomerCode(productCode, customerCode);
var updatedObject = new Builder(entityToUpdate).build(form);
var savedObject = repository.save(updatedObject); //error thrown here
return converter.convertToView(savedObject);
}
This is the Builder class implementation that appears above:
public class Builder {
private final Purchase entityToUpdate;
public Builder(Optional<Purchase> entityToUpdate) {
this.entityToUpdate = entityToUpdate.isPresent() ? entityToUpdate.get() : null;
}
public Purchase build(PurchaseUpdateForm form) {
this.entityToUpdate.setDiscount(form.getDiscount());
this.entityToUpdate.getId().setPurchaseBoxQuantity(form.getPurchaseBoxQuantity());
return entityToUpdate;
}
}
I tried to put an #Transactional in the update() and build() methods but the error persists. I also tried using saveAndFlush but nothing changed. Full log message:
Optimistic locking failed; nested exception is caused by: org.hibernate.StaleObjectStateException:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
[PurchasePK#bb7039e8]
# Additional Information - Domain
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_PURCHASE")
public class Purchase {
#EmbeddedId
private PurchasePK id;
#NotNull
#Column(name = "DISCOUNT")
private BigDecimal discount;
}
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(of = { "product", "purchaseBoxQuantity" })
#Embeddable
public class PurchasePK {
#NotNull
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CD_PRODUCT")
#JoinColumn(name = "CD_CUSTOMER")
private Product product;
#NotNull
#Column(name = "QT_BOXES")
private Long purchaseBoxQuantity;
}
The error you are seeing means Hibernate tried to update an entity, but the update statement actually updated 0 rows.
Hibernate assumes this is because the version attribute did not match, since for the purpose of optimistic locking all updates on entities with a version attribute have a where clause like the following.
WHERE id = ? and version = ?
The id = ? ensures that the correct row is updated. The version = ? ensures that the version hasn't changed. It does get changed with every update.
Since you don't have a version attribute in your entity, I'm somewhat surprised that you still get this error, but since you do, the only explanation is that the id does not match the one in the database.
So either the entity was deleted, or the id changed.
Make sure the id didn't get changed by accident.
Make sure the id gets passed to the database as expected. You can do that by enabling logging of SQL statements and parameters
Without having the full source, I can just guess that the problem is on your embedded id, specifically I guess it has to be with
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
I suggest you to add the following properties to your application properties file
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
so you can check how your entity's id is probably changed in a way you didn't expect to.

Hibernate ignoring discriminator column - always using 'dtype'

I have a strange situation in my SINGLE_TAB inheritance Hibernate config whereby the #DiscriminatorColumn seems to be ignored and the query is always defaulting back to the 'dtype' column. It's like the behaviour I would see when I had not included the annotation at all (the default column name being 'dtype').
Base entity:
#Entity
#Table(name = "post")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(columnDefinition = "post_type", discriminatorType = DiscriminatorType.STRING)
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "title")
private String title;
#Column(name = "body")
private String body;
#NotNull
#Column(name = "post_type", insertable = false, updatable = false)
private String postType;
// other simple columns
// ommit getters/setters + hashcode etc
}
Subclass entity:
#Entity
#DiscriminatorValue(value = "EVENT")
public class Event extends Post {
// ommitted basic methods, no extra config
}
I also need access to the discriminator value itself within each object (the postType field). I still have the same behaviour even if I remove it so it doesn't seem to be the cause.
When I try to do a query on the subclass through a JPA repository:
public interface EventRepository extends JpaRepository<Event, Integer> {
List<Event> findAll();
}
Hibernate generates the query:
select post0_.id as id2_4_, post0_.bodyl as body_bod3_4_, post0_.title as title12_4_
from post post0_
where post0_.dtype='EVENT'
which of course generates an error as 'dtype' doesn't exist in the table.
The strange thing is that if I use #DiscriminatorFormula("post_type") on the Post entity instead, everything seems to work. It is however slower so I would prefer to use the #DiscriminatorColumn as it should fit my needs exactly.
I am using Hibernate 5.2.10-FINAL and Spring Data JPA 1.11.4 (or generally the latest of hopefully everything).
Any ideas on what could be causing this?
I'm think you have this problem because you specified wrong parameter of #DiscriminatorColumn anotation, you should use name instead of columnDefinition.

Hibernate execute OneToMany if condition satisfied

I was wondering if it exist a way to tell to Hibernate to execute the query behind a #OneToMany only if a condition is satisfied.
This could be a very simple example:
#Entity
public class MyEntity {
...
private Long id;
private boolean condition;
private List<AnotherEntity> anotherEntity;
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Basic
#Column(name = "CONDITION")
public Boolean getCondition() {
return isContentChanged;
}
public void setIsContentChanged(Boolean condition) {
this.condition = condition;
}
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "SECOND_ID")
**#Conditional(clause = "CONDITION = true")**
public List<AnotherEntity> getAnotherEntity() {
return anotherEntity;
}
public void setAnotherEntity(List<AnotherEntity> anotherEntity) {
this.anotherEntity = anotherEntity;
}
}
#Entity
public class AnotherEntity {
private id
private secondId
...
}
So what I would like is:
- if the condition in the annotation #Conditional is satisfied, Hibernate execute the query related to the #OneToMany annotation
- if the condition in the annotation #Conditional is not satisfied, Hibernate do nothing with the #OneToMany annotation
I do not know if that already exist somewhere in Hibernate. Otherwise could be a great new feature.
One way you may be able to solve this depending on the context of AnotherEntity would be to use a Hibernate Filter. I have used something similar in the past where we had a OneToMany of a specific entity type but only certain values were to be returned based on user access.
Other ways may include using a #Formula or #Where annotation.
All three of these options are available in the documentation for Hibernate 5.2, starting here.
Update:
What I am about to explain only applies to associations which are explicitly defined as LAZY fetch types. If you keep the default EAGER fetch type or explicitly set it to EAGER, the following does not apply.
Given that your association is using LAZY fetch types, you could bake the condition as a part of your data access layer queries at runtime. There isn't an annotation to this so it would be part of your code but it at least allows you to minimize the join at runtime.
For situations where your condition is true, you either specify that the collection should be loaded EAGER if using the deprecated Hibernate Criteria API or you'll want to use a JOIN/JOIN FETCH if using the JPA Criteria API. Note that JOIN will only allow you to apply predicates again the collection to filter the root entities; however JOIN FETCH will also populate the collection for you.
This won't allow you to filter the collection results but it at least provides you a means to eliminate the join while allowing the join to be part of the query at runtime based on some condition.
This is very analygous where a search form has 10 searchable fields; however you only apply the appropriate predicates to a base query if a given field has a value; however no predicate and perhaps no specific joins are added to the base query if a value is not provided to improve runtime query performance.

Hibernate and #JoinFormula: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column

I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!

Why I'm getting Javassist objects in the middle of a result set?

I'm having an strange problem when I try to retrieve some entities from the database. The table where the entities lives just have 4 rows. When I try select all rows I get a list where the first and the last elements are loaded correct, however, the second and the third has all properties as null. Here is a print of my debug console:
The entity is simple, as you can see below:
#Entity
#Table(name = "Empresa")
public class Empresa implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_EMPRESA")
private Integer idEmpresa;
#Basic(optional = false)
#Column(name = "NOME_EMPRESA")
#OrderColumn
private String nomeEmpresa;
#Column(name = "CNPJ")
private String cnpj;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "iDEmpresa", fetch = FetchType.LAZY)
private List<Cadastro> cadastroList;
}
If you want know how I am retrieving the entities, here is the code:
#Override
public List<T> recuperarTodos() {
Query query = entityManager.createQuery(criarQueryRecuperarTodos());
limitarQuantidadeDeRegistros(query);
return query.getResultList();
}
private String criarQueryRecuperarTodos() {
StringBuilder builder = new StringBuilder("SELECT e FROM ");
builder.append(classe.getSimpleName());
builder.append(" e");
builder.append(criarParametrosOrdenacao());
return builder.toString();
}
This is perfectly legal and expected situation. Hibernate uses dynamically generated proxies (hence javaassist objects, in the past hibernate used cglib as well) as placeholders for not fully fetched entities to allow lazy fetching. Because of this, generally speaking, you should not attempt to access attribute values directly. Using getters instead allows hibernate to issue an appropriate DB query and fill the entity. This can be a problem in some situations - for example, if the values are first requested outside the Hibernate session.

Categories