How to persist a Map<String, List<Object>> in Hibernate - java

I've got a Map containing MyObject instances. The MyObject class uses JPA to persist its fields:
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
private Map<String, MyObject> results = new HashMap<String, MyObject>();
We changed the value stored by the Map to a List:
private Map<String, List<MyObject>> results = new HashMap<String, List<MyObject>>();
But upon launching we receive a stack trace:
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.me.myapp.MyObject.results[java.util.List]
at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1150)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:680)
at org.hibernate.cfg.annotations.MapBinder$1.secondPass(MapBinder.java:107)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1221)
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:383)
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1206)
at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:673)
at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:211)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1368)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1334)
... 30 more
Does Hibernate not support persisting a Map containing (as values) List types? Or are my annotations incorrect? I haven't found this particular configuration in any of the documentation or examples.

The objects stored in the map must be the target object of the oneToMany association (always mapped). You can't store arbitrary objects or collections there.

Personally, I don't think this is a good use of Hibernate. There's no abstraction here. It would make sense if you had a model object with a one-to-many relation expressed as a child List as a data member in the parent.
My advice? Don't use Hibernate. Use straight JDBC, Spring's JDBC template, or something like iBatis.
ORM stands for "Object Relational Mapping". You have tables, so you've got the relational part. You've got data that you can assign to columns in tables.
But it sounds to me like you've got no Objects. So why use Hibernate?

Related

Spring Data JPA - How to persist nested object any existing or not

Using Springboot 2.4.0, I am trying to develop a simple module to manage a tipical relationship between the entities Product and Order (ManyToMany relationship).
In Order, I have this products field:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name="products_orders",
joinColumns=#JoinColumn(name="order_id"),
inverseJoinColumns=#JoinColumn(name="product_id")
)
private List<Product> products;
Since I don't need it, for now I am omitting an orders field in the Product entity.
Previous to this relationship, I have a CRUD for Product entity up and properly running, so I can insert products through a Product Repository:
public interface ProductRepository extends JpaRepository<Product, Integer>
I need to place an order with a number of products within it. I have this code:
List<Products> myListOfProducts = new ArrayList<>();
myListOfProducts.add(previouslyExistingProduct);
myListOfProducts.add(nonExistingProduct);
Order myorder = new Order(myListOfProducts, "mail", LocalDateTime.now());
myorder.save();
The question is: is it possible to get with the previous code that the new order gets saved as well as the products linked to it (saving both the existing and non existing products)?
This is the Order Repository:
public interface OrderRepository extends JpaRepository<Order, Integer>
This is what I got so far:
If I change CascadeType to CascadeType.MERGE, I get to link only existing products to the new order, but for new Products, I get this error:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product
Any other value I put to CascadeType (including CascadeType.ALL), I get the opposite: new products are saved and linked to the new order, but for existing products, I get this error:
detached entity passed to persist:
com.gallelloit.productcrud.model.Product; nested exception is
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.gallelloit.productcrud.model.Product
I know that I can do it "manually" getting each of the products, and saving them before the order.save(), but I was wondering if Spring Data did it for you.
Any idea?
Relying on CascadeType.MERGE is not safe in this context, because it updates the associated Products as well, meaning you may unintentionally overwrite existing Product data.
You can optimize the fetching of existing products by asking JPA to create proxies for the existing entities instead of actually fetching their associated state from the DB. This can be done using JpaRepository.getOne(). You'll need sth like:
order.setProducts(order.getProducts()
.map(product -> product.getId() == null ? productRepository.save(product) : productRepository.getOne(product.getId())
.collect(Collectors.toList());
If you decide on using CascadeType.PERSIST or CascadeType.ALL, you can of course replace productRepository.save(product) with just product. CascadeType.ALL doesn't make sense for #ManyToMany, though, since it implies CascadeType.REMOVE, which you most certainly don't want.
Also, I'm assuming the logic of saving the order happens inside a transactional context, right?

How to add a HashMap<Object, String> in an entity class?

I am trying to create a sample Report card application and want to persist a map between subject and student's grade
This is my scorecard class:
#Entity
public class ScoreCard {
#NotNull #ElementCollection #ManyToMany(cascade=CascadeType.ALL)
private Map<Subject, String> gradesForMainSubject = new HashMap<Subject, String>();
}
But when trying to save data I always end up with
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: gradesForMainSubject
Subject itself is a Managed entity (annotated by #Entity). Any suggestions on how can I move forward.
You cannot use both #ElementCollection and #ManyToMany at the same time for a collection field.
If the values of your collection are entities, then you can use either one of the 2: #OneToMany or #ManyToMany
If the values of your collection are non-entities, then you must use #ElementCollection.
In your case, the values of your map are String which are not entities. Therefore you need to use #ElementCollection. Remove the #ManyToMany mapping. This rule should be followed, regardless of whether you map key is an entity or not.

Hibernate mapping #manytomany

I got 3 tables: Bus, Driver and BusDriver and I need to get bus entity with set of drivers. I have already understood how to do this but there is a date field in BusDriver and I need to include it in set. For example I got {bus_id, bus_model, ... {driver1, driver2}} but I need {bus_id, bus_model, ... {{driver1, date}, driver2, date}}
You'll need to map the BusDriver table to an entity to get this attribute.
You should do something like this exemplo
Generally you would do this by explicitly mapping BusDriver as an entity, then having one or both other entities map BusDriver as ManyToOne or OneToMany (as appropriate). Your model interface itself can obscure this if necessary by returning a Set or List of Bus (or Driver) instead of the mapping class.

How to Hibernate annotate Map<String, Set<String>> or Map<String, Map<String, String>>

using Hibernate Core 4.1.7, JPA annotations, java 1.7
Easy to fine are examples about Map<String, Entity> reading Hibernate doc on collections, or Map<String, String> here (stackoverflow).
Hard to find are examples on Map<String, Set<String>> (or even Map<String, Map<String, String>> just for curiosity about daisy chaning) why I ask this question here.
All I want is to save entities (accounts) containing named, multi-valued properties (=account attributes).
I have all working with 3 entity types: Account -> #OneToMany -> AccountAttribute -> #OneToMany -> AccountAttributeValue
But wrapping native Java types with my own Classes seems a bit silly to me. Cloning the Map<String, String> example I would like to have something like
#Entity
public class Account {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="key")
private Long key;
#ElementCollection(fetch=FetchType.EAGER)
#JoinTable(name = "Attribute",
joinColumns = #JoinColumn(name = "fkAccount"))
#MapKeyColumn(name = "name")
// next line is working for Map<String, String>, but not here!
#Column(name = "value")
private Map<String, Set<String>> attributes = new HashMap<String, Set<String>>();
// ... omitting: constructor, getters, setters, toString()
}
Which gives me
Initial SessionFactory creation failed: org.hibernate.MappingException: Could not determine type for: java.util.Set, at table: Attribute, for columns:[org.hibernate.mapping.Column(attributes)]
As DB layout I have created 2 Tables.
- Table Account just having a key to point foreign key to
- Table Attribute containing named value in each line.
E.g. for multivalued attributes I thought of it containing 2 lines with same fkAccount and name but different value - yes, I could have normalized even more, but I want to read my data in acceptable time :-)
CREATE TABLE IF NOT EXISTS `foo`.`Account` (
`key` INT NOT NULL AUTO_INCREMENT ,
...
CREATE TABLE IF NOT EXISTS `foo`.`Attribute` (
`fkAccount` INT NOT NULL ,
`name` VARCHAR(45) NOT NULL ,
`value` VARCHAR(45) NOT NULL
...
Any hints or alternate DB layout proposals appreciated.
EDIT - SOLVED
Solution from Tom (as far as I understood) working for me
Thanky you guys, what an experience, solution in 1 day!
The table layout just mentioned above in my question works now with this classes.
#Entity
public class Account {
/* ... omitting "key", see as above */
/* NEW: now #CollectionTable
replaces #JoinTable / #MapKeyColumn / #Column from above
*/
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="AccountAttribute",
joinColumns=#JoinColumn(name="fkAccount"))
private Set<AccountAttribute> attributes = null;
// ... omitting: constructor, getters, setters, toString()
}
and NEW
#Embeddable
public class AccountAttribute {
#Column(name="attributeName")
private String attributeName = null;
#Column(name="attributeValue")
private String attributeValue = null;
// ... omitting: constructor, getters, setters, toString()
}
JPA doesn't give you any way to map collections of collections of anything. You can map primitives, references to entities, embeddables, and collections of any of the preceding things.
Collection there means a set, list, or map; there isn't a multimap type here which would help you.
Therefore, there is sadly no way to map exactly the structure you want to map.
I think the closest you could come would be to define an embeddable class Attribute, containing a name and a value, then map a Set<Attribute>. You could then convert this to a Map<String, Set<String>> in code.
It's a shame there's no way to do this. I assume the JPA spec authors either didn't think of it, or thought it was an obscure enough corner case that it wasn't worth dealing with.
There's two ways to model this in the database, and it comes down to how many tables you want. If you want three, then the way you have working is basically right, although you could trim it to two entities (Account and AccountAttribute) where AccountAttribute contains a Set of values.
You can't model it with three tables and just an Account entity, because you don't have enough identifiers. The VALUE table would have to have a compound key made up of the account id and some kind of attribute key, your tables would have to look like:
ACCOUNT (id, ...)
ACCOUNT_ATTRIBUTE(account_id, account_attribute_id, ...)
ACCOUNT_ATTRIBUTE_VALUE(account_id, account_attribute_id, value, ...)
if AccountAttribute is an entity, then it has an ID. If not, it doesn't, and so how would you key the ACCOUNT_ATTRIBUTE_VALUE table?
This is borne out by the JPA spec, as mentioned in this other answer.
Now, you COULD do this in two tables with just an Account entity, by collapsing that Set in to some serialized form and persisting it as binary, or XML, or whatever. Not sure if that's worth the effort to you, but the column you sketched out (value varchar(45)) is almost certainly not long enough.

Efficiently determining the IDs of entities referenced via OneToMany relationship

Let's say I have a Hibernate entity that declares a OneToMany relationship to a different entity:
#Entity
public class SomeEntity {
#OneToMany(fetch = FetchType.LAZY)
private List<OtherEntity> otherEntities = new LinkedList<OtherEntity>();
[...]
}
When mapping SomeEntity to the corresponding DTO, all I need are the IDs that identify OtherEntity as primary key (i.e., I am not actually interested in OtherEntity instances).
Does Hibernate support this pattern, i.e., only retrieving the IDs of entities referenced via a OneToMany relationship?
I cannot influence how SomeEntity is retrieved (i.e., I have an existing SomeEntity instance retrieved within te scope of the current Hibernate session), but let's assume that lazy loading has not yet taken place, so just retrieving the child objects' IDs (rather than the complete objects) would actually yield a performance benefit.
Well, if you only need the entities' ids and you want to be economical about it, when you get those entities from the database you should state in your query that you only want to get the ids of each entry, using projections, something like :
SELECT Entity.id as entity FROM Entity WHERE ...
This will return an array of objects of the same type as Entity's id field type.
You can try obtaining the primary key without accessing the entity itself (without otherEntities.get(0).getId()). To do this you can use the PersistenceUnitUtil class:
PersistenceUnitUtil#getIdentifier(yourEntity)
The PersistenceUnitUtil can be obtained from the EntityManagerFactory. So it could be something like:
EntityManager em = ...
PersistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
Unfortunately, I'm not aware if this will prevent the entity loading from occuring. However, just accessing the otherEntities collection or even obtaining references to each entity will not make the instance to be loaded; you need to invoke a method on the fetched entity in order to be sure it will be loaded.
You also might consider creating a #NamedQuery and return only the OtherEntity ID's.
HTH!
From hibernate reference, section 2.2.2.1.
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-property
Declare your columns as lazy initialized:
#Basic(fetch = FetchType.LAZY)
private String getYourProperty() {
}
You also need to disable proxies for your entity class and byte instrument it. There is an example here:
Making a OneToOne-relation lazy
You can use the below HQL as told in the documentation to establish this.
session.createQuery(select new OtherEntity(oe.id) OtherEntity oe
where oe.parentSomeEntity.someId = :someId).list();//also set someId.
Add a constructor in OtherEntity to set the id also there should be a mapping to SomeEntity in OtherEntity.
This HQL will give you a List<OtherEntity> with only id set in the bean.

Categories

Resources