Auditing a map of child entities as values with Hibernate Envers - java

I have a Parent entity that refers a Child entity as a value in a map. The key in the map is an enum (see below for straightforward code). Unfortunately using #AuditJoinTable with a table name doesn't create the expected "parent_children_aud" table.
Is auditing for map references supported? Or is there something that I'm doing wrong?
Using Hibernate 3.6.0.
Parent class:
#Audited
public class Parent {
private Long id;
private Integer version;
private Map<MyEnum, Child> mappedChildren;
protected Parent() {}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
#AuditJoinTable(name = "parent_children_aud")
public Map<MyEnum, Child> getMappedChildren() {
return this.mappedChildren;
}
public void setMappedChildren(Map<MyEnum, TemplateStage> mappedChildren) {
this.mappedChildren = mappedChildren;
}
}
Child class:
#Audited
public class Child {
private Long id;
protected Child() {}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
MyEnum:
public enum MyEnum { AAA, BBB, CCC; }
hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
-//Hibernate/Hibernate Mapping DTD 3.0//EN
http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd>
<hibernate-mapping>
<class name="Parent" table="parents">
<cache usage="read-write"/>
<id name="id" column="id">
<generator class="native"/>
</id>
<version name="version" unsaved-value="negative"/>
<map name="mappedChildren" cascade="all-delete-orphan" lazy="true">
<cache usage="read-write"/>
<key column="parent_id"/>
<map-key type="MyEnum"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child" table="children">
<cache usage="read-write"/>
<id name="id">
<generator class="native"/>
</id>
<version name="version" unsaved-value="negative"/>
</class>
</hibernate-mapping>

Related

Hibernate: How to write Join queries including multi levels?

I am trying to write a HQL Query, which is similar to a MySQL Join. Below are my entities. As you can see below I am not using annotations in my Pojos. Instead I am using XML to do the mapping.
Stock
public class Stock implements java.io.Serializable {
private Integer idstock;
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Product product;
private int quantity;
private Date dateCreated;
private Date lastUpdated;
public Stock() {
}
public Stock(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public Stock(Product product, int quantity, Date dateCreated, Date lastUpdated) {
this.product = product;
this.quantity = quantity;
this.dateCreated = dateCreated;
this.lastUpdated = lastUpdated;
}
public Integer getIdstock() {
return this.idstock;
}
public void setIdstock(Integer idstock) {
this.idstock = idstock;
}
public Product getProduct() {
return this.product;
}
public void setProduct(Product product) {
this.product = product;
}
public int getQuantity() {
return this.quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Date getDateCreated() {
return this.dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
public Date getLastUpdated() {
return this.lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
}
Product
public class Product implements java.io.Serializable {
private Integer idproduct;
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private SparePart sparePart;
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private VehicleModel vehicleModel;
private double unitPrice;
private String qrcode;
private boolean enable;
private Integer minimumStockLevel;
private Integer stockReorderLevel;
public Product() {
}
public Product(SparePart sparePart, VehicleModel vehicleModel, double unitPrice, String qrcode, boolean enable) {
this.sparePart = sparePart;
this.vehicleModel = vehicleModel;
this.unitPrice = unitPrice;
this.qrcode = qrcode;
this.enable = enable;
}
public Product(SparePart sparePart, VehicleModel vehicleModel, double unitPrice, String qrcode, boolean enable, Integer minimumStockLevel, Integer stockReorderLevel) {
this.sparePart = sparePart;
this.vehicleModel = vehicleModel;
this.unitPrice = unitPrice;
this.qrcode = qrcode;
this.enable = enable;
this.minimumStockLevel = minimumStockLevel;
this.stockReorderLevel = stockReorderLevel;
}
public Integer getIdproduct() {
return this.idproduct;
}
public void setIdproduct(Integer idproduct) {
this.idproduct = idproduct;
}
public SparePart getSparePart() {
return this.sparePart;
}
public void setSparePart(SparePart sparePart) {
this.sparePart = sparePart;
}
public VehicleModel getVehicleModel() {
return this.vehicleModel;
}
public void setVehicleModel(VehicleModel vehicleModel) {
this.vehicleModel = vehicleModel;
}
public double getUnitPrice() {
return this.unitPrice;
}
public void setUnitPrice(double unitPrice) {
this.unitPrice = unitPrice;
}
public String getQrcode() {
return this.qrcode;
}
public void setQrcode(String qrcode) {
this.qrcode = qrcode;
}
public boolean getEnable() {
return this.enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public Integer getMinimumStockLevel() {
return this.minimumStockLevel;
}
public void setMinimumStockLevel(Integer minimumStockLevel) {
this.minimumStockLevel = minimumStockLevel;
}
public Integer getStockReorderLevel() {
return this.stockReorderLevel;
}
public void setStockReorderLevel(Integer stockReorderLevel) {
this.stockReorderLevel = stockReorderLevel;
}
}
VehicleModel
public class VehicleModel implements java.io.Serializable {
private Integer idvehicleModel;
private String modelName;
private String code;
private boolean enable;
public VehicleModel() {
}
public VehicleModel(String modelName, boolean enable) {
this.modelName = modelName;
this.enable = enable;
}
public Integer getIdvehicleModel() {
return this.idvehicleModel;
}
public void setIdvehicleModel(Integer idvehicleModel) {
this.idvehicleModel = idvehicleModel;
}
public String getModelName() {
return this.modelName;
}
public void setModelName(String modelName) {
this.modelName = modelName;
}
public boolean getEnable() {
return this.enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
SparePart
public class SparePart implements java.io.Serializable {
private Integer idsparePart;
private String sparePartName;
private String code;
private boolean enable;
public SparePart() {
}
public SparePart(String sparePartName, boolean enable) {
this.sparePartName = sparePartName;
this.enable = enable;
}
public Integer getIdsparePart() {
return this.idsparePart;
}
public void setIdsparePart(Integer idsparePart) {
this.idsparePart = idsparePart;
}
public String getSparePartName() {
return this.sparePartName;
}
public void setSparePartName(String sparePartName) {
this.sparePartName = sparePartName;
}
public boolean getEnable() {
return this.enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
Here are my XML mappings
Product.hbm.xml
<?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">
<!-- Generated Sep 4, 2020 1:35:36 PM by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
<class name="beans.Product" table="product" catalog="aaa" optimistic-lock="version">
<id name="idproduct" type="java.lang.Integer">
<column name="idproduct" />
<generator class="identity" />
</id>
<many-to-one name="sparePart" class="beans.SparePart" fetch="select">
<column name="idspare_part" not-null="true" />
</many-to-one>
<many-to-one name="vehicleModel" class="beans.VehicleModel" fetch="select">
<column name="idvehicle_model" not-null="true" />
</many-to-one>
<property name="unitPrice" type="double">
<column name="unit_price" precision="22" scale="0" not-null="true">
<comment>This is the central price for a product. This can change according to the market values.</comment>
</column>
</property>
<property name="qrcode" type="string">
<column name="qrcode" length="45" not-null="true" />
</property>
<property name="enable" type="boolean">
<column name="enable" not-null="true" />
</property>
<property name="minimumStockLevel" type="java.lang.Integer">
<column name="minimum_stock_level" />
</property>
<property name="stockReorderLevel" type="java.lang.Integer">
<column name="stock_reorder_level" />
</property>
</class>
</hibernate-mapping>
Stock.hbm.xml
<?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">
<!-- Generated Sep 4, 2020 1:35:36 PM by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
<class name="beans.Stock" table="stock" catalog="aaa" optimistic-lock="version">
<id name="idstock" type="java.lang.Integer">
<column name="idstock" />
<generator class="identity" />
</id>
<many-to-one name="product" class="beans.Product" fetch="select">
<column name="idproduct" not-null="true" />
</many-to-one>
<property name="quantity" type="int">
<column name="quantity" not-null="true" />
</property>
<property name="dateCreated" type="timestamp">
<column name="date_created" length="0" />
</property>
<property name="lastUpdated" type="timestamp">
<column name="last_updated" length="0" />
</property>
</class>
</hibernate-mapping>
SparePart.hbm.xml
<?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">
<!-- Generated Sep 4, 2020 1:35:36 PM by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
<class name="beans.SparePart" table="spare_part" catalog="aaa" optimistic-lock="version">
<id name="idsparePart" type="java.lang.Integer">
<column name="idspare_part" />
<generator class="identity" />
</id>
<property name="sparePartName" type="string">
<column name="spare_part_name" length="100" not-null="true" />
</property>
<property name="code" type="string">
<column name="code" length="100"/>
</property>
<property name="enable" type="boolean">
<column name="enable" not-null="true" />
</property>
</class>
</hibernate-mapping>
VehicleModel.hbm.xml
<?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">
<!-- Generated Sep 4, 2020 1:35:36 PM by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
<class name="beans.VehicleModel" table="vehicle_model" catalog="aaa" optimistic-lock="version">
<id name="idvehicleModel" type="java.lang.Integer">
<column name="idvehicle_model" />
<generator class="identity" />
</id>
<property name="modelName" type="string">
<column name="model_name" length="100" not-null="true" />
</property>
<property name="code" type="string">
<column name="code" length="100"/>
</property>
<property name="enable" type="boolean">
<column name="enable" not-null="true" />
</property>
</class>
</hibernate-mapping>
Right now I have the following query.
public List<Stock> getAllStock(Session session) {
Query query = session.createQuery("FROM Stock s");
List<Stock> list = (List<Stock>) query.list();
return list;
}
This gives me,
Stock
Product of each Stock
SparePart of each Product
VehicleModel of each Product
However this is extremely slow due to the famous n+1 issue. To get data from each table, this code generated a SQL query, resulting huge amount of sql queries. The more data you have, the more queries this generates . As a result, this is a super slow process. Currently it takes 40 seconds.
Instead I need to write HQL joins and get data with a single SQL query. How can I do this?
You should use JOIN FETCH to tell JPA/Hibernate that that it should load it.
From the Hibernate docs:
If you forget to JOIN FETCH all EAGER associations, Hibernate is going
to issue a secondary select for each and every one of those which, in
turn, can lead to N+1 query issues.
For this reason, you should prefer LAZY associations.
select s from Stock s join fetch s.product p
join fetch p.sparePart sp
join fetch p.vehicleModel v
Please also read the documentation: https://docs.jboss.org/hibernate/orm/5.5/userguide/html_single/Hibernate_User_Guide.html#best-practices-fetching-associations
You can use criteria implementation of hibernate and using alias you can join
Below is some reference code that may help
Criteria c = session.createCriteria(Stock.class, "stock");
c.createAlias("stock.product", "product");//it is like inner join
c.createAlias("product.spare_part","spare_part");
c.createAlias("product.vehicle_model","vehicle_model");
return c.list();

Repeated column in mapping for entity in hibernate XML mapping

I have been facing an issue of "Repeated column in mapping for entity". Could you kindly help me where i have done mistake on it. I have mentioned my code below.
USERAUDIT.hbm.xml
<hibernate-mapping>
<class name="com.mkyong.user.UserAudit" table="USER_AUDIT_TBL">
<id name="eventId" column="EVENT_ID" type="java.lang.Integer">
<generator class="sequence">
<param name="sequence">AUDIT_SEQUENCE</param>
</generator>
</id>
<property name="userId" type="java.lang.Integer">
<column name="USER_ID" length="10" not-null="true" unique="true" />
</property>
<set name="userAuditDtls" table="USER_AUTI_DTLS_TBL" inverse="true"
lazy="true" fetch="select">
<key>
<column name="EVENT_ID" not-null="true" />
</key>
<one-to-many class="com.mkyong.user.UserAuditDtls" />
</set>
</class>
</hibernate-mapping>
USERAUDIT.java
enter code herepublic class UserAudit implements java.io.Serializable {
private Integer eventId;
private Integer userId;
//private UserAuditDtls userAuditDtls;
private Set<UserAuditDtls> userAuditDtls =
new HashSet<UserAuditDtls>(0);
public UserAudit(Integer eventId, Integer userId, Set<UserAuditDtls> userAuditDtls) {
super();
this.eventId = eventId;
this.userId = userId;
this.userAuditDtls = userAuditDtls;
}
public UserAudit() {
super();
// TODO Auto-generated constructor stub
}
public Integer getEventId() {
return eventId;
}
public void setEventId(Integer eventId) {
this.eventId = eventId;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Set<UserAuditDtls> getUserAuditDtls() {
return userAuditDtls;
}
public void setUserAuditDtls(Set<UserAuditDtls> userAuditDtls) {
this.userAuditDtls = userAuditDtls;
}
}
USERAUDITDTLS.hbm.xml
<hibernate-mapping>
<class name="com.mkyong.user.UserAuditDtls" table="PII_USER_AUTI_DTLS_TBL">
<id name="eventId" type="java.lang.Integer">
<column name="EVENT_ID" />
<generator class="foreign">
<param name="property">userAudit</param>
</generator>
</id>
<many-to-one name="userAudit" class="com.mkyong.user.UserAudit" fetch="select">
<column name="EVENT_ID" not-null="true" />
</many-to-one>
<property name="fieldName" type="string">
<column name="FIELD_NAME" length="100" not-null="true" />
</property>
</class>
</hibernate-mapping>
UserAuditDtls.java
public class UserAuditDtls implements java.io.Serializable {
private Integer eventId;
private UserAudit userAudit;
private String fieldName;
public UserAuditDtls() {
super();
// TODO Auto-generated constructor stub
}
public UserAuditDtls(Integer eventId, UserAudit userAudit, String fieldName) {
super();
this.eventId = eventId;
this.userAudit = userAudit;
this.fieldName = fieldName;
}
public Integer getEventId() {
return eventId;
}
public void setEventId(Integer eventId) {
this.eventId = eventId;
}
public UserAudit getUserAudit() {
return userAudit;
}
public void setUserAudit(UserAudit userAudit) {
this.userAudit = userAudit;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
}
Main.Java
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
UserAudit audit = new UserAudit();
audit.setUserId(new Integer(100));
session.save(audit);
UserAuditDtls auditDtls = new UserAuditDtls();
auditDtls.setFieldName("Small");
auditDtls.setUserAudit(audit);
audit.getUserAuditDtls().add(auditDtls);
session.save(auditDtls);
session.getTransaction().commit();
Error:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.mkyong.user.UserAuditDtls column: EVENT_ID (should be mapped with insert="false" update="false")
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:676)
at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:698)
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:720)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:474)
at org.hibernate.mapping.RootClass.validate(RootClass.java:235)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1335)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1838)
at com.mkyong.util.HibernateUtil.buildSessionFactory(HibernateUtil.java:13)
Tables:
USER_AUDIT_TBL
event_id - pk
userid -integer
USER_AUDIT_DTLS_TBL
event_id- fk
fieldname - varchar
problem is <column name="EVENT_ID" not-null="true" /> in USERAUDITDTLS.hbm.xml

Select and order list by other condition(Criteria inner join hibernate)

Supposing that we create 2 tables with below SQL :
create table Supplier (id int, name VARCHAR, count int);
create table Product (id int, name VARCHAR, description VARCHAR, price double, supplierId int);
Models:
public class Supplier {
private int id;
private String name;
private int count;
public int getId(){ return id;}
public void setId(int id){ this.id = id; }
public String getName(){ return name;}
public void setName(String name){ this.name = name;}
public int getCount() { return count;}
public void setCount(int count) { this.count = count;}
}
AND
public class Product {
private int id;
private String name;
private String description;
private Double price;
private Supplier supplier;
public int getId() { return id;}
public void setId(int id) { this.id = id; }
public String getName() { return name;}
public void setName(String name) { this.name = name;}
public String getDescription() { return description;}
public void setDescription(String description) { this.description = description; }
public Double getPrice() {return price;}
public void setPrice(Double price) { this.price = price;}
#OneToOne(targetEntity=ProductAssignment.class, mappedBy = "supplierId", fetch = FetchType.LAZY)
public Supplier getSupplier() { return supplier;}
public void setSupplier(Supplier supplier) { this.supplier = supplier; }
}
If I want to select all products order by count in supplier I can use the below code :
Criteria crit = session.createCriteria(Product.class);
Criteria critSupplier = crit.createCriteria("supplier");
critSupplier.addOrder(Order.desc("count"));
But now, I want to select all suppliers order by price in Product table.
if I want to use MySQL, the below is the script:
select * from supplier s inner join product p ON s.id = p.supplierId order by p.price
Now I want to transfer this SQL into Hibernate Criteria query in java code?
Please help me in this case?
Here you have a bidirectional relationship between two models: Supplier and Product. It is a bidirectional relationship since you want both the models to be aware of each other, and recollect each other information, based on the link that joins them (supplierId). The relationship is also a one(Supplier)-toMany(Products)
So, first off, you are missing the fact that also Supplier must be aware of the existence of the relationship. You have to express this "awareness" by modifying the Supplier model and add to it the list products:
public class Supplier implements Serializable{
private int id;
private String name;
private int count;
private List<Product> products;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
#Override
public String toString() {
return "Supplier{" + "name=" + name + '}';
}
The second step is to communicate the ORM(in your case hibernate) the relationship between your two models. Online you can find plenty of documentation that explains this subtle "step" of hibernate. in your case, something like this should do.
Hibernate mapping of Supplier:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.xxx.stackoverflowdb.model.Supplier" table="Supplier">
<id column="id" name="id" type="int">
<generator class="assigned"/>
</id>
<property column="name" name="name" type="string"/>
<property column="count" name="count" type="int"/>
<bag name="products" table="product" inverse="true" lazy="false" fetch="select">
<key>
<column name="id"/>
</key>
<one-to-many class="com.xxx.stackoverflowdb.model.Product"/>
</bag>
</class>
</hibernate-mapping>
Hibernate mapping of Product:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.xxx.stackoverflowdb.model.Product" table="PRODUCT">
<id column="id" name="id" type="int">
<generator class="assigned"/>
</id>
<property column="name" name="name" type="string"/>
<property column="description" name="description" type="string"/>
<property column="price" name="price" type="double"/>
<many-to-one name="supplierId" class="com.xxx.stackoverflowdb.model.Supplier" column="supplierId" insert="false" update="false" lazy="false"/>
</class>
</hibernate-mapping>
As you can see, both mapping files declare the relationship. With this set, you can write the Criteria and have it do the job. Since it now hibernate knows about the relationship, it can help you. I've created a simple tester class that demonstrates it:
public class Tester {
public static void main(String[] args) {
//gets a session, assuming your cg file is in a folder called hibernate_dispatcher
//under classpath
SessionFactory sessionFactory = new Configuration().configure("/hibernate_dispatcher/hibernate.cfg.xml")
.buildSessionFactory();
Session session = sessionFactory.openSession();
//gets a session, assuming your cg file is in a folder called hibernate_dispatcher
//under classpath
//YOUR own query --> gets all products order by count in supplier
Criteria criteria1 = session.createCriteria(Product.class);
criteria1.createAlias("supplierId", "supp");
criteria1.addOrder(Order.desc("supp.count"));
for(Object p:criteria1.list()){
Product nthP=(Product)p;
System.out.println(nthP);
}
//YOUR own query --> gets all products order by count in supplier
//the query you've asked --> gets all products order by price in Product
Criteria criteria2 = session.createCriteria(Supplier.class);
criteria2.createAlias("products", "prod");
criteria2.addOrder(Order.desc("prod.price"));
for(Object s:criteria2.list()){
Supplier nthS=(Supplier)s;
System.out.println(nthS);
}
//the query you've asked --> gets all products order by price in Product
}
}

Hibernate/Spring: Not-null property references a null or transient value

I'm developing an application using Hibernate, Spring and GWT in Java. I used reverse engineering under Hibernate (JBoss Developer Studio used) to obtain POJOs and configuration files from an existing MySQL database. It's very simple database with only two entities: Country and Citizen. They have OneToMany relationship between.
Here is the code:
app entry point:
...
Country country = new Country();
country.setName("NameOfCountry"+i);
country.setPopulation(10000);
Citizen ctz = new Citizen();
ctz.setName("John");
ctz.setSurname("Smith");
ctz.setCountry(country);
country.getCitizens().add(ctz);
service.saveCitizen(ctz, new AsyncCallback<Boolean>(){
#Override
public void onFailure(Throwable caught) {
System.out.println("Problem saving citizen");
}
#Override
public void onSuccess(Boolean result) {
System.out.println("Citizen successfully saved");
}
});
service.saveCountry(country, new AsyncCallback<Boolean>(){
#Override
public void onFailure(Throwable caught) {
System.out.println("Problem saving country");
}
#Override
public void onSuccess(Boolean result) {
System.out.println("Country successfully saved");
}
});
...
-- service provides simple GWT-RPC call to server
Service on server:
#Service("componentService")
public class ComponentServiceImpl implements ComponentService{
#Autowired
private CountryDAO daoCnt;
#Autowired
private CitizenDAO daoCtz;
#Transactional(readOnly=false)
#Override
public boolean saveCitizen(Citizen citizen) {
daoCtz.saveOrUpdate(citizen);
return true;
}
#Transactional(readOnly=false)
#Override
public boolean saveCountry(Country country) {
daoCnt.saveOrUpdate(country);
return true;
}
}
Now SpringDAOs:
CitizenDAO:
#Repository
public class CitizenDAO {
...
public void saveOrUpdate(Citizen citizen){
sessionFactory.getCurrentSession().saveOrUpdate(citizen);
}
...
CountryDAO:
#Repository
public class CountryDAO {
...
public void saveOrUpdate(Country country){
sessionFactory.getCurrentSession().saveOrUpdate(country);
}
...
Finally
Citizen.hbm.xml:
<hibernate-mapping>
<class name="sk.jakub.mod.shared.model.Citizen" table="citizen" catalog="modeldb">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<many-to-one name="country" class="sk.jakub.mod.shared.model.Country" fetch="select">
<column name="Country_id" not-null="true" />
</many-to-one>
<property name="name" type="string">
<column name="name" length="45" not-null="true" />
</property>
<property name="surname" type="string">
<column name="surname" length="45" not-null="true" />
</property>
</class>
</hibernate-mapping>
Country.hbm.xml:
<hibernate-mapping>
<class name="sk.jakub.mod.shared.model.Country" table="country" catalog="modeldb">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="name" type="string">
<column name="name" length="45" not-null="true" />
</property>
<property name="population" type="int">
<column name="population" not-null="true" />
</property>
<set name="citizens" table="citizen" inverse="true" lazy="true" fetch="select">
<key>
<column name="Country_id" not-null="true" />
</key>
<one-to-many class="sk.jakub.mod.shared.model.Citizen" />
</set>
</class>
</hibernate-mapping>
I havent listed Citizen.java and Country.java because they are only basic POJOs (if necessary I'll provide them).
When I launch my app and I want to save my data into database I obtain following error:
org.hibernate.PropertyValueException: not-null property references a null or transient value: sk.jakub.mod.shared.model.Citizen.country
I can't figure out where is the problem. I was trying also instead of saveOrUpdate method, persist method. Or also to change the order of saving into database. Nothing seemed to work.
Thank you very much for help :) If needed, I can post more code from my application.
EDIT:
code for Citizen.java:
public class Citizen implements java.io.Serializable {
private static final long serialVersionUID = -3102863479088406293L;
private Integer id;
private Country country;
private String name;
private String surname;
public Citizen() {
}
public Citizen(Country country, String name, String surname) {
this.country = country;
this.name = name;
this.surname = surname;
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public Stat getCountry() {
return this.country;
}
public void setCountry(Country country) {
this.country = country;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return this.surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
}
Country.java:
public class Country implements java.io.Serializable {
private static final long serialVersionUID = -4085805854508658303L;
private Integer id;
private String name;
private int population;
private Set<Citizen> citizens = new HashSet<Citizen>();
public Country() {
}
public Country(String name, int population) {
this.name = name;
this.population = population;
}
public Country(String name, int population, Set<Citizen> citizens) {
this.name = name;
this.population = population;
this.citizens = citizens;
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getPopulation() {
return this.population;
}
public void setPopulation(int population) {
this.population = population;
}
public Set<Citizen> getCitizens() {
return this.citizens;
}
public void setCitizens(Set<Citizen> citizens) {
this.citizens = citizens;
}
}
Furthermore, I've checked the database manually and Country is saved but citizen is not.
I am seeing that you are creating a Citizen before you create a country. Also both the service calls should be in same transaction for the whole operation to be atomic. The COUNTRY_ID seems to be a self generated id i believe. So once you create the country you can attach that to a citizen but you call stack shows you are creating a citizen which has a Country object which doesnt have an id. This is just my guess. You can try putting both the calls under same transaction and also try creating a Country and attach that country instance to the Citizen.
Please check if you have implemented the equals, hashcode and compareTo (if applicable) methods properly. I have recently faced this problem and resolved it by proper implemetation of these.

Hibernate many-to-many mapping not saved in pivot table

I having problems saving many to many relationships to a pivot table.
The way the pojos are created is unfortunately a pretty long process which spans over a couple of different threads which work on the (to this point un-saved) object until it is finally persisted. I associate the related objects to one another right after they are created and when debugging I can see the List of related object populated with their respective objects. So basically all is fine to this point. When I persist the object everything get saved except the relations in the pivot table.
mapping files:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.thebeansgroup.jwinston.plugin.orm.hibernate.object">
<class name="ShowObject" table="show_object">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set cascade="all" inverse="true" name="venues" table="venue_show">
<key column="show_id"/>
<many-to-many class="VenueObject"/>
</set>
</class>
</hibernate-mapping>
and the other
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.thebeansgroup.jwinston.plugin.orm.hibernate.object">
<class name="VenueObject" table="venue_object">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="latitude" type="integer"/>
<property name="longitude" type="integer"/>
<set cascade="all" inverse="true" name="shows" table="venue_show">
<key column="venue_id"/>
<many-to-many class="ShowObject"/>
</set>
</class>
</hibernate-mapping>
pojos:
public class ShowObject extends OrmObject
{
private Long id;
private String name;
private Set venues;
public ShowObject()
{
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Set getVenues()
{
return venues;
}
public void setVenues(Set venues)
{
this.venues = venues;
}
}
and the other:
public class VenueObject extends OrmObject
{
private Long id;
private String name;
private int latitude;
private int longitude;
private Set shows = new HashSet();
public VenueObject()
{
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public int getLatitude()
{
return latitude;
}
public void setLatitude(int latitude)
{
this.latitude = latitude;
}
public int getLongitude()
{
return longitude;
}
public void setLongitude(int longitude)
{
this.longitude = longitude;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Set getShows()
{
return shows;
}
public void setShows(Set shows)
{
this.shows = shows;
}
}
Might the problem be related to the lack of annotations?
Couple things to try:
You have inverse="true" on both ends of many-to-many. It should be only at one end.
Make your sets not lazy.
You didn't specify column property for many-to-many tag.
So it should look something like this at the end:
<class name="ShowObject" table="show_object">
...
<set lazy="false" cascade="all" name="venues" table="venue_show">
<key column="show_id"/>
<many-to-many class="VenueObject" column="venue_id" />
</set>
</class>
<class name="VenueObject" table="venue_object">
...
<set lazy="false" cascade="all" inverse="true" name="shows" table="venue_show">
<key column="venue_id"/>
<many-to-many class="ShowObject" column="show_id"/>
</set>
</class>

Categories

Resources