Jpa Hibernate map key in multi column relationship - java

I would like to map a Java Map, where all key values are stored in the same table.
Something similar to https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Example_of_a_map_key_column_relationship_database
but with the key being an object and not a simple type.
Say I have an Entity "User"
#Entity
public class User{
#Id
private String userId;
#OneToMany
#MapKeyClass(CalenderWeek.class)
private Map<CalenderWeek, WorkedTime> workedTimeMap;
The key CalendarWeek would be something like this
#Embeddable
public class CalenderWeek {
int year;
Month month; // Month is the enum java.time.Month
The WorkedTime would be something like
#Embeddable
public class WorkedTime {
private long workedHours;
The corresponding worked time table should be like this
worked_time
user_id | year | month | worked_hours
---------|------|-------| ---
1 | 2017 | 11 | 42
Is it possible to get that
or do I have to do it as described here
https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Example_of_a_map_key_class_embedded_relationship_annotation
i.e., with three tables.

In general if you want to have a Map u simply use #ElementCollection annotation and if you want to override some of the column or associations from the Embeddables then you use #AttributeOverride / #AssociationOverride:
#ElementCollection
#AttributeOverrides({
#AttributeOverride(name="key.year",
column=#Column(name="YEAR1")),
#AttributeOverride(name="value.workedHours",
column=#Column(name="WORKED_H"))
})
private Map<CalenderWeek, WorkedTime> workedTimeMap;
Depending whether you want to override the key or value attribute, you add those prefixes respectively for persistence provider to be able to recodgnize the difference.

The answer of Maciej Kowalski was very useful and correct. Thank you.
I just want to complete here the answer and extend it a little as I used xml configuration at the end.
The Annotated version ended like that
#Entity
public class UserWorklogAggregate {
#Id
private String userId;
#ElementCollection
#CollectionTable(
name = "worked_time",
joinColumns = #JoinColumn(name = "user_id")
)
#AttributeOverrides({
#AttributeOverride(name = "key.year", column = #Column(name = "year")),
#AttributeOverride(name = "key.week", column = #Column(name = "week")),
#AttributeOverride(name = "value.workedDuration", column = #Column(name = "worked_duration"))
})
private final Map<CalenderWeek, WorkedTime> workedTimeMap = new HashMap<>();
and the xml version
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field" default-cascade="all">
<class name="org.whatever.UserWorklogAggregate" table="user_worklog_aggregate">
<id name="userId" length="10" column="user_id" />
<map name="workedTimeMap" table="worked_time" >
<key column="user_id" />
<composite-map-key class="org.whatever.CalenderWeek">
<key-property name="year" column="year"/>
<key-property name="week" column="week"/>
</composite-map-key>
<composite-element class="org.whatever.WorkedTime" >
<property name="workedDuration" column="worked_duration"/>
</composite-element>
</map>
</class>

I assume you want to store working hours of a user per month+year.
Looking at your result table I would recommend not using Embeddable. You could try this approach:
#Entity
public class User{
#Id
private String id;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<WorkedTime> workedTime;
plus the back reference:
#Entity
public class WorkedTime {
#Id
private long id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
private int year;
private Month month;
private long workedHours;
This would result in having only two tables.

Related

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

Hibernate "Column --- cannot be null

I'm using Hibernate in combination with phpmyadmin (MySQL).
Recently I found a really weird error.
Whenever I try to insert a new row (.persist) I get the following error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null
The error is really obvious.
But the weird part is, this error came out of nowhere.
I'm ENTIRELY sure the property userId (mapped to 'user_id') isn't null. I tested several times.
This is the mapping part:
(On the #manytoone part).
<property name="userId" update="false" insert="false" column="user_id" type="java.lang.Long" />
The OneToMany part isn't the problem I guess.
So the problem here is, i'm 100% sure the value isn't null, still Hibernate passes it as null to the MySQL. (Getter does work).
UPDATE
#ManyToOne side, which causes the error
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="xx.xx.xx.AlarmEntity" table="alarms">
<meta attribute="class-description">
Hierin staan de object properties van de alarm entiteit.
</meta>
<id name="id" type="java.lang.Long" column="id">
<generator class="native"/>
</id>
<property name="keepsRunning" type="java.lang.Boolean">
<column name="keeps_running" sql-type="int"></column>
</property>
<property name="userId" update="false" insert="false" column="user_id" type="java.lang.Long" />
<many-to-one name="userAlarm" cascade="save-update" fetch="select" column="user_id" class="xx.xx.xx.UserEntity" />
</class>
</hibernate-mapping>
Please check the mapping
UserEntity
private int user_id;
private Set<AlarmEntity> alarm;
AlarmEntity
private int alarm_id;
private String keepsRunning;
As per your requirement , I have provided mapping with annotation please check
User
#Id
#Column(name = "userId")
private Long userId;
// parent to Alarm
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "userId")
#JsonIgnore
private List<Alarm> alarmList ;
Alarm
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "alarmId")
private Long alarmId;
#ManyToOne
#JoinColumn(name = "userId", insertable = true, updatable = true, nullable = true)
private User user;
#Column(name="keepsRunning")
private String keepsRunning;
Providing Annotation Approach one to Many Example which worked for me
In below example Factory can have multiple product ,that means there is one to many mapping between Factory and Product where Product is owning side of this relationship which means Product will maintain foreign key constraint for holding factory id.
Table structure:
CREATE TABLE `factory` (
`id` bigint NOT NULL AUTO_INCREMENT,
`factory_name` varchar(500) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`factory_id` bigint NOT NULL ,
`product_name` varchar(500) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE product
ADD CONSTRAINT FK_product_factory
FOREIGN KEY (factory_id) REFERENCES factory (id);
Entity Structure:
#Entity
#Table(name = “factory”)
public class Factory {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name=“id”)
private int id;
#Column(name = "factory_name")
private String factoryName;
#OneToMany(mappedBy = “factory”,cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Product> products = new ArrayList<>();
}
#Entity
#Table(name = “product”)
public class Product {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = “product_name")
private String productName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name=“factory_id")
private Factory factory;
}
Service Structure:
#Service
public class ProductService {
#Autowired
private ProductRepository productRepository;
#Autowired
private FactoryRepository factoryRepository;
public void saveFactoryAndProduct(){
Factory factory = new Factory();
factory.setFactoryName("F1");
Product product1 = new Product();
product1.setProductName("P1");
factory.getProducts.add(product1);
product.setFactory(factory);
Product product2 = new Product();
product2.setProductName("P2");
factory.getProducts.add(product2);
product1.setFactory(factory);
//Saving factory will also saved linked product1 and product2 data having factory_id as a foreign key from Factory table
factoryRepository.save(factory);
}
}
Note:
Saving Products with Mapped Factory.Here we don't need to save product specifically since Factory is maintaining cascade so when we save factory related products will also save but we just need to setFatory for every product object inorder to maintain foreign key in product.

One object from two tables

I have two tables and want to map them to one object with Hibernate.
The reason for both tables is in the past and I wont change the frontend, which access data like this.
I have Table Event (Event_ID, Preview, img) and Event_Details (ID, Event_ID, content).
I prepare one class in Java:
public class Event {
private int event_ID;
private String preview;
private String img;
private String content;
//Getter and Setter
}
and following XML mapping file:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 16.03.2016 20:33:10 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
<class name="de.data.events.Event" table="ev1_event">
<id name="id" type="int">
<column name="Event_ID" />
<generator class="assigned" />
</id>
<property name="preview" type="java.lang.String">
<column name="ev1_preview" />
</property>
<property name="img" type="java.lang.String">
<column name="ev1_img" />
</property>
</class>
<class name="de.data.events.Event" table="pb1_event">
<id name="id" type="int">
<column name="id" />
<generator class="assigned" />
</id>
//some properties
</class>
The part, where I have to join table1 to table2 is missing. But I didn´t found a way to fix my problem.
First, you'd have your Hibernate entities. Yours are Event and EventDetail, but for fun, let's go with Person and Address in this example. The key here is that you need a one-to-one or many-to-one relationship between your tables. Otherwise, your resultset will come out weird (more on that later).
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PERSON_ID")
public Integer id;
#Column(name = "NAME")
public String name;
#Column(name = "HOME_ADDRESS_ID")
public Integer homeAddressId;
#Column(name = "TEMP_ADDRESS_ID")
public Integer tempAddressId;
// . . . other fields,getters,setters
}
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ADDRESS_ID")
public Integer id;
#Column(name = "STREET")
public String street;
#Column(name = "CITY")
public String city;
// . . . other fields,getters,setters
}
Then you'd have your target FlatObject POJO with the a constructor that will build it appropriately:
public class FlatObject {
public final String name;
public final String homeStreet;
public final String homeCity;
public final String tempStreet;
public final String tempCity;
public FlatEntity(String name, String homeStreet, String homeCity, String tempStreet, String tempCity) {
this.name = name;
this.homeStreet = homeStreet;
this.homeCity = homeCity;
this.tempStreet = tempStreet;
this.tempCity = tempCity;
}
// . . . other fields,getters
}
Finally, you'd leverage those objects with a Hibernate HQL SELECT where you join Person and Address and use their fields to construct a new FlatEntity:
SELECT new FlatEntity(p.name, ha.homeStreet, ha.homeCity, ta.tempStreet, ta.tempCity)
FROM
Person p, Address ha, Address ta
WHERE
p.homeAddressId = ha.id
and p.tempAddressId = ta.id
As you can see, the HQL statment will join Person to Address twice: once for the home address and once for the temp address.
The same will hold true in your case. So in your case, if you're joining Event e, EventDetail ed WHERE e.id = ed.eventId, just be sure there's only one detail row per Event. Otherwise you'll get multiple rows when you have more than one details or you'll drop rows (because of the inner join) when an event has no details.

Polymorphic association jpa2 hibernate

I think im doing something wrong but i cant get working #any annotation on hibernate 4.2.2 with jpa2 1.0.1
The class works ok, but i cant get join.
My code is this:
#Entity(name = "conta")
public class Conta {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "empresa_id")
private int empresaId;
private String descricao;
#Any(metaColumn = #Column(name = "tipo"), fetch = FetchType.EAGER)
#AnyMetaDef(idType = "int", metaType = "string", metaValues = {
#MetaValue(value = "BANCO", targetEntity = Banco.class),
#MetaValue(value = "CIELO", targetEntity = Cielo.class)
})
#JoinColumn(name = "financeira_id")
private Financeira financeira;
//getters and setters
}
Financeira its an interface, Banco.class and Cielo.class implements Conta as well. When i try to fetch from DB i can get all records, but the join doesnt happen.
My code inspection on Intellij IDEA says for private Financeira financeira that "'Basic' attribute type should not be 'Financeira'". I know or i think i know that this message says that jpa dont have metamodel for #any annotation, but hibernate does, so, this should work right?
UPDATE
Got the problem solved by using hibernate native xml configuration.
Now i can persist my objects but i got another problem. I cant fetch the association, im using a factory to fetch de association on #postLoad event, but thats not the right way.
above my xml code.
<any id-type="java.lang.Integer" meta-type="string" name="tipo" cascade="all">
<meta-value value="BANCO" class="br.com.leaftecnologia.lfadmin.model.financeiro.BANCO" />
<meta-value value="PLAYSMS" class="br.com.leaftecnologia.lfadmin.model.financeiro.CIELO" />
<column name="tipo" />
<column name="financeira_id" /></any>

JPA mapping to Map<Object, Interger>

I've 3 tables:
- lots: has column gen_id
- onhand_quantities: has column gen_id,sub_inventory_code, quantity
- subinventory: has column sub_inventory_code
I map to 2 objects: Lots, SubInventory and I would like to mapping in object Lots with property is: Map<SubInventory, Integer> getOnhandQuantity()
Integer type in this Map is value of quantity column.
Please help me.
Just a brief search in Hibernate document give me information on using an associated entity as key of a Map (aka Ternary Association)
Quoted from the manual:
There are three possible approaches to mapping a ternary association.
One approach is to use a Map with an association as its index:
Example 7.31. Ternary association mapping
#Entity
public class Company {
#Id
int id;
...
#OneToMany // unidirectional
#MapKeyJoinColumn(name="employee_id")
Map<Employee, Contract> contracts;
}
// or
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map>
A second approach is to remodel the association as an entity class.
This is the most common approach. A final alternative is to use
composite elements, which will be discussed later.
To map it back to your original example: Company -> Lots, Employee -> SubInventory, Contract -> OnhandQuantity
Personally I would rather make them as a simple relationship, and construct Map etc on the fly whenever it is needed.
Haven't tried, but a further look get me to an example in #MapKeyColumn, which in combination with the above sample, should give something reasonable like:
class Lot {
#ElementCollection
#MapKeyJoinColumn(name="sub_inventory_id")
#CollectionTable(name="onhand_quantities")
#Column(name="quantity")
private Map<SubInventory, Integer> onhandQuantities;
}
The below code in the Lots entity should do.
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "ONHAND_QUANTITIES", joinColumns= #JoinColumn(name="GEN_ID"))
#MapKeyJoinColumn(name="SUBINVENTORY_CODE")
#Column(name="QUANTITY")
private Map<SubInventory, Integer> onHandQuantity;
I am trying to test this.
I got this solution from the below link.
Reference key issue while doing many to many relationship using #ElementCollection, #MapKeyJoinColumn
#Entity
#Table(name = "LOTS")
public class Lots implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "GEN_ID")
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "ONHAND_QUANTITIES", joinColumns= #JoinColumn(name="GEN_ID"))
#MapKeyJoinColumn(name="SUBINVENTORY_CODE")
#Column(name="QUANTITY")
private Map<SubInventory, Integer> onHandQuantity;
......// getters and setters
}
#Entity
#Table(name = "SUBINVENTORY")
public class SubInventory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="SUBINVENTORY_CODE")
private Long id;
......// getters and setters
}

Categories

Resources