Create a persistent object using a transient one - java

I have 2 mappings like this:
<hibernate-mapping>
<class name="A" table="A">
<id name="code" column="aCode" type="integer">
<generator class="assigned"/>
</id>
<property name="info" type="integer"/>
<set name="Bs" lazy="false">
<key>
<column name="aCode" not-null="true"/>
</key>
<one-to-many class="B"/>
</set>
</class>
<class name="B" table="B">
<id name="code" column="bCode" type="integer">
<generator class="assigned"/>
</id>
<many-to-one name="a"/>
</class>
</hibernate-mapping>
These are the classes:
public class A {
private int code;
private int info;
private Set<B> bs = new HashSet<B>(0);
public A() {};
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public int getInfo() { return info; }
public void setInfo(int info) { this.info = info; }
public Set<B> getBs() { return bs; }
public void setBs(Set<B> bs) { this.bs = bs; }
}
public class B {
private int code;
private A a;
public B() {};
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public A getA() { return a; }
public void setA(A a) { this.a = a; }
}
I'm in a scenario where I have to deal with a long transition and do the following:
// Persistence Layer
Session session = factory.getCurrentSession();
session.beginTransaction();
A a1 = new A(); // Create transient object
a1.setCode(1);
a1.setInfo(10);
session.save(a1); // Persist it
// Something happening in another layer (see below)
// Continuing with the transaction
Object b = ... // Recover object
session.save(b); // Persist it using transient a2 object as a link but don't change/update its data
System.out.println(b.getA().getInfo()); // Returns 0 not 10;
session.commit();
This happens in another layer (don't have access to the session here):
// * Begin in another layer of the application *
A a2 = new A(); // Create another transient object same *code* as before
a2.setCode(1);
B b = new B(); // Create another transient object
b.setCode(1);
b.set(a2);
// * End and send the b Object to the persistence layer *
Is there any way load/get the persistent child object before saving parent one or is there other way to save the child object without change the info and flush it all? I'm not using JPA. Sorry if I'm terrible wrong.
Thank you.

Currently state of the new child is not saved into the database since your relationship doesn't have cascading, so wrong state of the child shouldn't be a big problem.
However, if you want to have consistent state of the entities in memory, you can use merge() instead of save(), without cascading it should work exactly as desired:
b = session.merge(b); // Persist it using transient a2 object as a link but don't change/update its data
System.out.println(b.getA().getInfo()); // Should return 10
See also:
Hibernate: will merge work with many-to-one object transtively?

I think what you want to do is:
A a2 = (A)session.get(A.class, 1);

Related

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.

#IdClass with non primative #Id

I'm trying to add a composite primary key to a class and having a bit of trouble. Here are the classes.
class User {
private long id;
...
}
class Token {
private User user;
private String series;
...
}
I'm using the orm.xml to map the classes because they're actually part of a higher level API that I don't want to depend on JPA - it has a number of implementations.
Here it is:
...
<entity class="User">
<attributes>
<id name="id">
<generated-value strategy="AUTO"/>
</id>
...
</attributes>
</entity>
<entity class="Token">
<id-class class="TokenPK"/>
<attributes>
<id name="series"/>
<id name="user"/>
<many-to-one name="user"/>
</attributes>
</entity>
Finally to make it all work, I've created the TokenPK class and it looks like this:
public class TokenPK implements Serializable {
private String series;
private User user;
public TokenPK() {
}
public TokenPK(String series, User user) {
this.series = series;
this.user = user;
}
public String getSeries() {
return series;
}
public void setSeries(String series) {
this.series = series;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RememberMeTokenPK that = (TokenPK) o;
if (!series.equals(that.series)) return false;
if (!user.equals(that.user)) return false;
return true;
}
#Override
public int hashCode() {
int result = series.hashCode();
result = 31 * result + user.hashCode();
return result;
}
}
The problem I'm having is that Hibernate is complaning that it can't create the mysql tables because 'BLOB/TEXT column 'user' used in key specification without a key length'.
My issue is actually that the columns are being stored as BLOBs in the first place. Until I put the id-class in it was working just fine, user was linked via it's id. How can I make Hibernate use the long value for the user's id it was using as the primary key?
Updated orm.xml:
<entity class="Token">
<id-class class="TokenPK"/>
<attributes>
<id name="series"/>
<id name="user">
<column name="userId"/>
</id>
<many-to-one name="user">
<join-column name="userId" insertable="false" updatable="false"/>
</many-to-one>
</attributes>
</entity>
Define the composite key with String series and int userId, and specify a join-column id for the User in Token. I think you will also have to add insertable="false", updatable="false".
<composite-id name="TikenPK" class="yourpackage.TokenPK">
<key-property name="series" column="series" type="string" />
<key-property name="userId" column="userId" type="integer"/>
</composite-id>

Hibernate bidirectional parent/child problem

I'm having a problem implementing a bi-directional parent/child relationship using hibernate 3. The parent, in this case is of the class ReportCriteria. The child is of class PkVisit. I've pasted my hibernate configuration files as well as the underlying java objects below.
ReportCriteria configuration:
<hibernate-mapping package="org.fstrf.masterpk.domain">
<class name="ReportCriteriaBean" table="masterPkReportCriteria">
<id name="id" column="id">
<generator class="org.hibernate.id.IncrementGenerator" />
</id>
<bag name="pkVisits" table="masterPkWeeks" cascade="all-delete-orphan" inverse="true">
<key column="runId"/>
<one-to-many class="PkVisit"/>
</bag>
</class>
</hibernate-mapping>
ReportCriteria bean:
public class ReportCriteriaBean {
private Integer id;
private List<PkVisit> pkVisits = LazyList.decorate(new ArrayList(), FactoryUtils.instantiateFactory(PkVisit.class));
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public List<PkVisit> getPkVisits() {
return pkVisits;
}
public void setPkVisits(List<PkVisit> pkVisits) {
this.pkVisits = pkVisits;
}
}
PkVisit Configuration:
<hibernate-mapping package="org.fstrf.masterpk.domain">
<class name="PkVisit" table="masterPkWeeks">
<id name="id" column="id">
<generator class="org.hibernate.id.IncrementGenerator" />
</id>
<many-to-one name="reportCriteriaBean" class="ReportCriteriaBean" column="runid" not-null="true" />
<property name="week" column="week" />
</class>
</hibernate-mapping>
PkVisit Bean:
public class PkVisit {
private Integer id;
private ReportCriteriaBean reportCriteriaBean;
private Integer week;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public ReportCriteriaBean getReportCriteriaBean() {
return reportCriteriaBean;
}
public void setReportCriteriaBean(ReportCriteriaBean reportCriteriaBean) {
this.reportCriteriaBean = reportCriteriaBean;
}
public Integer getWeek() {
return week;
}
public void setWeek(Integer week) {
this.week = week;
}
}
The problem occurs when I try to save an instance of ReportCriteria, which, due to the cascade should also save any child PkVisits as well. However, when the save is called using
hibernateTemplate.saveOrUpdate(reportCriteria);
The following error is generated:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value: org.fstrf.masterpk.domain.PkVisit.reportCriteriaBean; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value: org.fstrf.masterpk.domain.PkVisit.reportCriteriaBean
When I save a report criteria that contains no PkVisits then everything works as I would expect, but as soon as any elements are in the pkVisits list of the ReportCriteria bean the errors occurs.
SOLUTION EDIT:
My problem was that I was never explicitly setting the parent (ReportCriteriaBean) in the children (PkVisits). I remedied the problem by editing my PkVisits setter in the following way:
public void setPkVisits(List<PkVisit> pkVisits) {
this.pkVisits = pkVisits;
for(PkVisit visit : pkVisits){
visit.setReportCriteriaBean(this);
}
}
It appears that you are not creating the bidirectional link in java properly. I'd recommend creating an add method on ReportCriteriaBean; something to the effect of:
public boolean add(PkVisit pkVisit) {
boolean added = false;
added = getPkVisits().add(pkVisit);
if (added) {
pkVisit.setReportCriteriaBean(this);
}
return added;
}
The error indicates that you cannot save a PkVisit if its ReportCriteriaBean is null. The above code, i think, is your missing link. If you go this route, you just add the PkVisit to the ReportCriteriaBean before persisting the report criteria and all should be well.
Also, here's a link to the hibernate documentation on this subject, section 21.2
Check if the PkVisit is generated ok, prior to the saveOrUpdate() call.
Then, you may need to eager fetch reportCriteriaBean/pkVisits where you have the hibernate session, prior to accessing them where you don't have hibernate session:
Hibernate.initialize(reportCriteriaBean.getPkVisits());

Can I specify a hibernate relationship with a filter?

I have a foo that has a relationship to many bar's.
When I delete bar's in my system I want to keep them in the database for some crazy business reason so I just set a deleted field to true.
Can I specify in my hibernate mapping that I only want my collection to hold elements where that field is false?
You can also use an SQL statement to keep stuff out, using the where attribute: Example bit of your hibernate mapping file for a set relationship:
<set name="specialConditions" cascade="none" order-by="sortOrder, Text1"
where="Discriminator in ( 'SPECIAL-CURF-AGREEMENT' ) and active = 'Y'"
sort="unsorted" inverse="false" mutable="true" optimistic-lock="true"
embed-xml="true">
<key column="parentID" not-null="false" on-delete="noaction" />
<one-to-many class="au.gov.abs.maserati.domain.entity.Condition" not-found="exception" embed-xml="true" />
</set>
Hibernate provides filters to accomplish this.
Example beans:
public class Foo {
private Long id;
private String text;
private Set<Bar> bars = new HashSet<Bar>();
// constructors, getters, setters
}
public class Bar {
private Long id;
private boolean deleted;
private String text;
// constructors, getters, setters
}
Example mappings NB: the filter element
<hibernate-mapping package="org.nkl.hib">
<class name="Foo">
<id name="id" column="FOO_ID">
<generator class="sequence" />
</id>
<property name="text" />
<set name="bars" cascade="all" fetch="join">
<key column="FOO_ID" />
<one-to-many class="Bar" />
<filter name="deleted" condition=":deleted = deleted" />
</set>
</class>
<filter-def name="deleted">
<filter-param name="deleted" type="boolean" />
</filter-def>
</hibernate-mapping>
<hibernate-mapping package="org.nkl.hib">
<class name="Bar">
<id name="id" column="BAR_ID">
<generator class="sequence" />
</id>
<property name="text" />
<property name="deleted" />
</class>
</hibernate-mapping>
Example unit test:
public class FooBarTest {
private static SessionFactory sessionFactory;
#AfterClass
public static void closeSessionFactory() {
sessionFactory.close();
}
#BeforeClass
public static void setupSessionFactory() {
Configuration configuration = new Configuration();
configuration.configure();
sessionFactory = configuration.buildSessionFactory();
}
#Test
public void testBarFilter() {
doInTransaction(new Command() {
public void execute(Session session) {
Foo foo = new Foo("foo");
foo.addBar(new Bar("bar1"));
foo.addBar(new Bar("bar2"));
foo.addBar(new Bar("bar3"));
session.save(foo);
}
});
doInTransaction(new Command() {
public void execute(Session session) {
Bar bar = (Bar) session.createQuery(
"from Bar b where b.text = 'bar2'").
uniqueResult();
bar.setDeleted(true);
session.update(bar);
}
});
doInTransaction(new Command() {
public void execute(Session session) {
session.enableFilter("deleted").
setParameter("deleted", Boolean.FALSE);
Foo foo = (Foo) session.createQuery("from Foo").
uniqueResult();
assertEquals(2, foo.getBars().size());
}
});
}
private void doInTransaction(Command command) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
command.execute(session);
tx.commit();
session.close();
}
}
interface Command {
public void execute(Session session);
}

Categories

Resources