Hibernate does not keep DB order in the LinkedHashSet collection - java

When retrieving values from DB hibernate does not keep insertion order in collections of type Set but keeps it in List collections. I've tried to specify LinkedHashSet manually but it does not help.
I have below parent entity
#Entity
#Table(name = "radio")
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")
public class Radio extends AbstractField {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "radio_id")
private Set<Choice> choices = new LinkedHashSet<>();
}
And child entity:
#Entity
#Table(name = "choice")
public class ChoiceEntity {
#Id
#GeneratedValue
#Column(name = "id", nullable = false)
private UUID id;
#Column(name = "value")
private String value;
}
When I am retriving values from the DB, hibernate stores them in choices collection in random order. When I've changed collection type to use List everything works fine.
Is it possible to configure hibernate to keep DB order in the collections of type Set without using any addition order_row.

Use #javax.persistence.OrderBy or #org.hibernate.annotations.OrderBy. The former expects a fragment defined in HQL/JPQL whereas the latter expects a SQL fragment

Related

Hibernate Multiple #OneToMany bound to same entity type

I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/

Hibernate: Limit result size of child objects

I've got a parent entity which has many child objects, most of which are collections. See below.
#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "ID")
private long id;
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "A_ID", nullable = false)
private Set<B> setB;
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "A_ID", nullable = false)
private Set<C> setC;
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "A_ID", nullable = false)
private Set<D> setD;
// Skipping more collections as they are not needed for the example
// Standard getters and setters
}
Now classes B, C and D have A_ID and 5 more String columns. Please consider them B1,B2,B3,B4,B5 and so on.
I also have a CrudRepository for A
#Repository
public interface ARepository extends CrudRepository<A, Long> {
Optional<A> findById(Long id);
}
I want to fetch the complete A object with an id but the other child collections that A have (setB, setC, setD) contains approx thousands of rows for each A_ID. I want to put a filter to fetch only first 100 rows for a given A_ID.
I have tried putting #Where(clause = "ROWNUM < 101") on the collections but it does not work as in the query the table name gets prefixed to the ROWNUM.
I also took a look at Criteria and Criterion but I am unable to find any working solution.
Since there are many collection of Objects in the parent class. So using native queries for each object would be too much rework.
Can anyone please help me with this. Do comment if you need more information.
Thanks
That's not so easy. You would have to fetch the collections manually or use a library like Blaze-Persistence Entity-Views on top that supports this out of the box. Also see here for a similar question: Hibernate - Limit size of nested collection
In your particular case, this could look like the following:
#EntityView(A.class)
public interface ADto {
#IdMapping
long getId();
#Limit(limit = "100", order = "id asc")
Set<BDto> getSetB();
#Limit(limit = "100", order = "id asc")
Set<C> getSetC();
#Limit(limit = "100", order = "id asc")
Set<D> getSetD();
}

Cascade persist creates duplicate rows?

I'm creating a database entity object Order, and assign it to multiple entities of type BookingCode.
Problem: this creates a single order in db, which is fine. But the order itself has a #OneToOne OrderDescription, which occurs duplicate in the database.
#Entity
public class BookingCode {
#Id
private Long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH})
private Order order;
}
#Entity
public class Order {
#Id
private Long id;
private String orderName;
#OneToOne(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private OrderDescription description;
}
#Entity
public class OrderDescription {
#Id
private Long id;
//for simplicity just one text element; of course multiple fields in real life
private String text;
#OneToOne
private Order order;
}
Test:
Order order = new Order();
order.setOrderName("test");
OrderDescription d = new OrderDescription("testdescr");
d.setOrder(order);
order.setDescription(d);
List<BookingCodes> codes = new ArrayList<>();
BookingCode code = new BookingCode();
code.setOrder(order);
codes.add(order);
BookingCode code2 = new BookingCode();
code2.setOrder(order); //using the same offer entity!
codes.add(order2);
codes = dao.save(codes); //CrudRepository from Spring
dao.findOne(codes.get(0).getId()); //this works, find an order which has one of the OrderDescriptions
Result:
In my database I then have two OrderDescription entries, where I would expect only one, because I reused the same Order object and assigned it to different BookingCode objects.
Like:
table order_descrption:
1;"de";"testdescr";"123456"
2;"de";"testdescr";"123456"
As Order has a #OneToOne relation to OrderDescription
And I even don't understand why the select using findOne() works correctly. Because in database I now have two OrderDescriptions that map to the same Order, but an Order can only have one of them.
Persist the order first and then assign it to both bookingCode .
I had a similar issue where I had an Order obj and its variable prevOrder was referring to itself i.e. Order entity. And when I stored order, it would end up storing duplicate records for prevOrder.
I had the following code:
#Entity
#Table(name = "orders")
public class Order implements Serializable {
#Id
#GeneratedValue(generator = "order_id_generator")
#SequenceGenerator(name = "order_id_generator", sequenceName = "order_id_sequence", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.ALL, optional = true)
#JoinColumn(name = "previous_order_id", unique = true, updatable = false, referencedColumnName = "id")
private Order previousOrder;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "previousOrder")
private Order nextOrder;
...
I tried various things including overriding equals and hashcode of Order, and adding a OneToOne mappedBy field 'nextOrder' etc. But noticed JPA didn't even call equals() to determine object's uniqueness. Ultimately I found out that JPA uses id field as the object's identifier and I wasn't storing the generated id while storing the object to a distrobuted cache. So it was all the time creating fresh objects during persistence.

How to use entity field in Hibernate #Formula

Many times I'm using #Formula in my entities. But always it was a simple query or stored procedure with parameter which I can take as filed from table. Now I need to user some property from related object. But I see exception when try to get object from DB. Please see an example below
#Entity
#Table(name = "MINISTRY")
public class Ministry {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
// unnecessary code
}
#Entity
#Table(name = "DEPARTMENT")
public class Department {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DEP_NAME")
private String departmentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MINISTRY_ID")
private Ministry ministry;
// unnecessary code
}
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPARTMENT_ID")
private Department department;
#Formula("test_package.calc_something(department.ministry.id)")
private BigDecimal someMetric;
// unnecessary code
}
How I should use entity prop in #Formula.
I don't want to write something like
select d.ministry.id from Department d ...
If you read the JavaDoc of Formula you will see:
The formula has to be a valid SQL fragment
So you will have to use SQL like:
#Formula("test_package.calc_something("
+ "select DEP.MINISTRY_ID from DEPARTMENT DEP where DEP.ID = DEPARTMENT_ID"
+ ")")
private BigDecimal someMetric;
The only thing that is modified by Hibernate in the fragment before writing it to SQL: It will add the table alias to your columns (as you can't predict that). I mention that, as only a rudimentary SQL parser is used for that, which will insert the alias at wrong positions for more complex fragments.
A remark about performance: The formula is executed for every Department entity that you load, even if you only want to use the attribute for sorting or filtering (just guessing from the name of the attribute) - unless you use #Basic(fetch = FetchType.LAZY) and turn bytecode instrumentation on (or emulate that with FieldHandled).

Usage of #IndexColumn results in a seq_num of 0

I would like to make use #IndexColumn to set seq number of some data the user enters. I am using Spring 2.5.6, JBoss 5.1 (JPA 1.0).
For my parent class
#Entity
#Table(name="material")
public class Material implements Serializable {
.
.
/**
* List of material attributes associated with the given material
*/
#OneToMany(mappedBy = "material", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#IndexColumn(name="seq_number", base=0, nullable = false)
private List<MaterialAttribute> materialAttributes;
public void addMaterialAttribute(List<MaterialAttribute> attribs)
{
if(CollectionUtils.isNotEmpty(attribs))
{
for(MaterialAttribute attrib : attribs)
{
attrib.setMaterial(this);
}
this.setMaterialAttributes(attribs);
}
}
}
For my child class
#Entity
#Table(name="material_attribute")
public class MaterialAttribute implements Serializable
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "material_id", referencedColumnName = "id", updatable=false, nullable = true, unique = false)
private Material material;
#Column(name = "seq_number", insertable=false, updatable=false, nullable = false)
private int seqNumber;
}
For the service class
public void save(MaterialCommand pCmd)
{
Material material = new Material(pCmd.getName());
//convert from command object to entity object
List<MaterialAttribute> attribs = new ArrayList<MaterialAttribute>();
if(CollectionUtils.isNotEmpty(pCmd.getAttribs()))
{
Iterator<MaterialAttributeCommand> iter = pCmd.getAttribs().iterator();
while(iter.hasNext())
{
MaterialAttributeCommand attribCmd = (MaterialAttributeCommand) iter.next();
MaterialAttribute attrib = new MaterialAttribute();
attrib.setDisplayName(attribCmd.getDisplayName());
attrib.setValidationType(attribCmd.getValidationType());
attribs.add(attrib);
}
}
material.addMaterialAttribute(attribs);
this.getMaterialDAO().saveMaterial(material);
}
I am getting entries into the database but the seq_number is always zero, for every item in the collection.
I have to assume it is in the way that I am saving the data but I just do not see it.
I have been able to solve the issue doing the following (removed the mappedBy):
#Entity
#Table(name="material")
public class Material implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5083931681636496023L;
#Column(name="name", length=50, nullable=false)
private String mName;
/**
* List of material attributes associated with the given material
*/
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#IndexColumn(name="seq_number", base=0)
#JoinColumn(name="material_id",nullable=false)
private List<MaterialAttribute> materialAttributes;
#Entity
#Table(name="material_attribute")
public class MaterialAttribute implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -196083650806575093L;
/**
* identifies the material that these attributes are associated with
*/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "material_id", insertable=false, updatable=false, nullable = true, unique = false)
private Material material;
#Column(name = "seq_number", insertable=false, updatable=false)
private int seqNumber;
Mapping a bidirectional indexed List with Hibernate is a bit tricky but is covered in the section 2.4.6.2.1. Bidirectional association with indexed collections of the documentation (bold is mine):
2.4.6.2.1. Bidirectional association with indexed collections
A bidirectional association where one
end is an indexed collection (ie.
represented as a #OrderColumn, or as
a Map) requires special
consideration. If a property on the
associated class explicitly maps the
indexed value, the use of mappedBy
is permitted:
#Entity
public class Parent {
#OneToMany(mappedBy="parent")
#OrderColumn(name="order")
private List<Child> children;
...
}
#Entity
public class Child {
...
//the index column is mapped as a property in the associated entity
#Column(name="order")
private int order;
#ManyToOne
#JoinColumn(name="parent_id", nullable=false)
private Parent parent;
...
}
But, if there is no such property on
the child class, we can't think of
the association as truly
bidirectional (there is information
available at one end of the
association that is not available at
the other end: the index). In this
case, we can't map the collection as
mappedBy. Instead, we could use the
following mapping:
#Entity
public class Parent {
#OneToMany
#OrderColumn(name="order")
#JoinColumn(name="parent_id", nullable=false)
private List<Child> children;
...
}
#Entity
public class Child {
...
#ManyToOne
#JoinColumn(name="parent_id", insertable=false, updatable=false, nullable=false)
private Parent parent;
...
}
Note that in this mapping, the
collection-valued end of the
association is responsible for
updating the foreign key.
Actually, the second mapping is precisely how to map a bidirectional one to many with the one-to-many side as the owning side. While this is possible, you need to be aware that this kind of mapping will produce under optimized SQL as stated in the section about 2.2.5.3.1.1. Bidirectional [One-to-many] relations:
To map a bidirectional one to many,
with the one-to-many side as the
owning side, you have to remove the
mappedBy element and set the many to
one #JoinColumn as insertable and
updatable to false. This solution is
not optimized and will produce some
additional UPDATE statements.
To sum up, if mapping the index column as a property of the target entity is not a concern, this would be my recommendation (i.e. the first mapping).
References
Hibernate Annotations 3.4 Reference Guide
2.2.5.3.1.1. Bidirectional [One-to-many]
2.4.6.2.1. Bidirectional association with indexed collections

Categories