Model O has an element collection of an enum type.
The abbreviated version
#Entity
class O {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ElementCollection(fetch=FetchType.EAGER)
#JoinTable(name = "o_s", joinColumns = { #JoinColumn(name = "o_id") })
#Column
private Set<SomeEnum> ss;
}
I am querying for all instances of O as follows
List<O> ret=session.createCriteria(O.class).list();
Now the result list contains duplicate entries.
If, there are 3 values in SS field, then the corresponding entry for O will appear 3 times in the result.
If there are 2 values, then the corresponding entry for O will appear 2 times in the result.
However, the database does not contain duplicate entries.
I have verified this behavior empirically.
What am I doing wrong?
That's caused by your eagerly loaded collection. You need to set the DictinctRootEntityResultTransformer to the criteria.
A better alternative, IMO, would be to use HQL:
select distinct o from O o
Related
I have 2 entities with oneToMany relationship. I want to maintain the insertion order for child entity. I used #orderColumn for that. Code:
Parent Class:
#Entity
public class Order{
private String orderId;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#NotEmpty
#OrderColumn
private List<OrderItem> orderItems = new ArrayList<>();
}
Child class:
#Entity
public class OrderItem{
#Id
private String orderItemId;
#ManyToOne
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
The issue that I'm facing here is orderColumn is not backward compatible. i.e. it adds an column in the child table with name "order_item_order". It works fine for the records that are getting created after this change but for the previous records, the column is null and it results in below exception:
org.hibernate.HibernateException: null index column for collection
I have tried setting the default value to 0 for the column. In that case it returns only one record for child.
Suggestions please.
You have two solutions :
Proceed with the #OrderColumn but fill it with the right values : index starting at 0, incrementing by 1 (migrate your data thanks to a sql scripts or a two steps migration from java)
Proceed with #OrderBy annotation : add a creation_date column, fill it when you store the object (like in the create(ModelClass model) method of your repository) and set it to a default value in the past
There is 2 models with relation many-to-many:
#Entity
public class Map {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private long mapId;
#NotBlank
private String title;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "route_points",
joinColumns = #JoinColumn(name = "mapId", referencedColumnName = "mapId"),
inverseJoinColumns = #JoinColumn(name = "pointId", referencedColumnName = "pointId"))
private Set<Point> points;
}
#Entity
public class Point {
#Id
private String pointId;
#NotBlank
private String city;
#ManyToMany(mappedBy = "points")
private Set<Map> maps;
}
I have to save an order of points in the set and record it to the intermediate table. How it can be done? Thx.
Use a List instead of a Set in combination with the #OrderColumn annotation:
https://docs.oracle.com/javaee/6/api/javax/persistence/OrderColumn.html
This will create an additional column in route_points to track the order of elements, including insertion, deletion, and reordering.
Please note that JPA will maintain the ordering as consecutive numbers, so most (if not all) structural modifications to the list will result in an update statement for each and every element.
Set basicly does not hold order of insertion. LinkedHashSet however will keep order of insertion of elements you could try that. It will store Point into database in proper order, but most probably it again will be mixed up after fetching from the database. You have to viable options here:
Dont use Set use List insteed.
Stick with Set and add private Integer index field to Pointand store proper indexes - you will be able to sort that after fetch without any problems
If you ensure that points will be persisted in right order into databse, then you could ommit additional column and sort by id assuming you are using unique, no gaps, autogenerated sequence for PKs
I have a Order entity, and a Product entity. An order may have a number of pairs, representing the product and the number sold. What is an approprate relation in JPA to represent it?
(So far I have only found methods to associate a collection of EntityA with EntityB. e.g. EntityA contains a List<EntityB>. )
If the quantity is all there is to this association and you do not need to navigate from Product→Order, you can consider the Integer quantity as an element collection and do the following - Product stays the same:
public class Order {
#ElementCollection // 1
#CollectionTable(name="ORDER_PRODUCT_QTY") // 2
#MapKeyJoinColumn(name="PRODUCT_ID") // 3
#Column(name="QUANTITY") // 4
private Map<Product, Integer> quantities;
}
It is a collection of basic types (integers for the quantity), keyed by the entity
It is multivalued, so needs a separate table; you optionally want to specify its name
The separate collection table will contain column(s) pointing to the Order entity, column(s) pointing to the Product and a column for the quantity value itself. This lets you set the name of the FK referencing the Product table and is optional.
This lets you specify the name of the column holding the quantity value. Optional too.
If you have reasons to believe that this is not enough then you may want to create a distinct entity representing the association, like:
Order ← OrderItem → Product
Order has many OrderItems, Product has many OrderItems, Order has many Products indirectly through OrderItem, Product can be found in many Orders, indirectly through OrderItem and the quantity is in the OrderItem. Representing this kind of "relation with value" as an entity is more flexible than collection mapping.
you have to map entity like
In Order.jave
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_Id")
#JsonBackReference
private List<Product> product = new ArrayList<Product>();
In Product.jave
#ManyToOne
#JoinColumn(name = "product_Id", referencedColumnName = "product_Id", nullable = false, insertable = false, updatable = false)
#JsonBackReference
private Order order;
I am also using the above code but it is not working for me.
Below is my code.
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="content_package_component_level_languages_language_assessment_map", joinColumns=#JoinColumn(name="id"))
#MapKeyJoinColumn(name="language_assessment_map_key", referencedColumnName = "id")
#Column(name="language_assessment_map")
private Map<Lang, Integer> languageAssessmentMap;
I am trying to map entities so I'll have following or similar effect (preferably without OrderItem.quantity) :
Here is my entity :
public class Orders implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private UserCreds user;
#OneToMany
private List<Item> orderedItems;
I end up with OrdersItem join table mapped with just 2 columns :
item_id and order_id, both are keys
thus it wont let me persist order with repeating items. Adding id column for OrderItems should do the trick:
OrdersItem table that I except:
| ID | ORDER_ID | ITEM_ID
1 25 31
2 25 31
3 25 12
4 25 12
5 25 62
etc..
But I just couldnt get that working, or maybe my solution is completely wrong?
Using an element collection here might be a better option. An element collection works well for providing relationships with attributes.
It could look like this in your Orders class:
#ElementCollection
private Map<Item, Integer> itemQuantities = new HashMap<Item, Integer>();
EDIT: You can use #ElementCollection when the value type of the Map is a basic type or an Embeddable. Integer is a basic type.
If you decide to use an entity as the value type of the Map, you have to use #OneToMany or #ManyToMany
The key type has no influence on the annotation selection in this case, so you could use an Item or a Long as key, without having to change the annotation. This does however have an impact on the physical mapping annotations you can use.
I jave the following mapped superclass that provides a basic implementation for a parent/child self relationship to create a parent/child list for unlimited nesting of items (i.e. Categories)
#MappedSuperclass
public abstract class ParentChildPathEntity<N extends ParentChild> implements MaterializedPath<N> {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "parent_id")
private N parent;
#Column(name = "name", unique = true)
private String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<N> children = new HashSet<N>();
If I load the entire table with fetch join on the parent and children, a single select loads all the records and i can happily traverse the tree. my problem comes in when i specify to retrieve a node on the tree. i want the node and all its children in a single select. below is the hql for loading the entire table:
hql.append(String.format("tree from %s tree ", tableName));
hql.append("left join fetch tree.parent ");
hql.append("left join fetch tree.children ");
if i specify the node name, i.e.:
where tree.name = :name
then hibernate retrieves the node, but when i access the children i get the SELECT N+1 issue. I realize why this is happening, (because of the tree.name = :name) but is there a way to write the HQL so it loads the specified node and all its children?
I'm just trying to figure out a way to support a simple nested item's list where i can retrieve any parent node and its children with a single select
thanks in advance,
Have you tried using the #BatchSize annotation?
#BatchSize(size = 20)
Ex:
#OneToMany(mappedBy = ..., fetch = FetchType.LAZY)
#BatchSize(size = 20)
public SortedSet<Item> getItems() { ... }
Then, if you specify the join to children in your HQL, you should be able to avoid n+1 select. I am not sure, offhand, if there is a way to specify the batch size in the HQL statement.