How to add Restrictions to Criteria API in Hibernate - spring

I am newbie to Hibernate and Spring. My question is on “ how to add criteria…” in situation like, I have two beans:
1) Language:
public class Language {
private int languageId;
private String languageName;
private List<Topic> listOfAllTopics;
}
2) Topic:
public class Topic {
private int topicId;
private String topicName;
}
My database tables:
1) language
language_id int(PK)
language_name varchar(30)
2) topic
topic_id int(PK)
topic_name varchar(30)
language_id int(FK)
Hibernate mapping xml files are:
1) Language.hbm.xml
<hibernate-mapping package="com.mw.javamentordb.dto">
<class name="Language" table="language" lazy="true">
<id name="languageId" column="language_id" type="int">
<generator class="increment" />
</id>
<property name="languageName" column="language_name"type="string"/>
<bag name="listOfAllTopics" cascade="all">
<key column="language_id" not-null="true" />
<one-to-many class="Topic"/>
</bag>
</class>
</hibernate-mapping>
2) Topic.hbm.xml
<hibernate-mapping package="com.mw.javamentordb.dto">
<class name="Topic" table="topics" lazy="true">
<id name="topicId" column="topics_id" type="int">
<generator class="increment" />
</id>
<property name="topicName" column="topics_name" type="string" />
</class>
</hibernate-mapping>
And I get all lang in my database using this method and it works properly.
public List<Language> getAllLanguages() {
Criteria criteria = getSession().createCriteria(Language.class);
return criteria.list();
}
But when I try to get all topics of particular language(by langId) using criteria, it not works.
Actually I don’t know how to use criteria in such kind of situation..
public List<Topic> getAllTopicOfLanguage(Language language) {
Criteria criteria = getSession().createCriteria(Topic.class);
criteria.add(Restrictions.eq("?");
return criteria.list();
}

Your table cheme for Topic has a foreign key constraint language_id but your class and hbm.xml mapping does not.
So your desired query is not possible.
Change it to:
public class Topic
{
private int topicId;
private String topicName;
private Language language;
}
And at the property to hbm.xml:
<many-to-one name="language" class="package.Language" fetch="select">
<column name="language_id">
</many-to-one>
Then you can query it using criteria like following:
criteria.add(Expression.eq("language.language_id", language.getLanguageId()));
Alternatively you could use the equality on the object itself instead their id's or use Expression.eqId(Object Object)
Advice:
Use a abstract superclass with the identifier field to make things more generic. Naming the identifier once topicId on class and table Topic and languageId on class and table Language just is overhead. Just use id on property, class and table to make things easier.
In larger applications this will become more obvious.

you are searching your result through wrong way, as per your design, you will get the result through,
public Language getAllTopicOfLanguage(Language language) {
Criteria criteria = getSession().createCriteria(Language.class);
criteria.add(Expression.eq("languageId",language.getLanguageId()));
return (Language)(criteria.list().get(0));
}
this will return you Language DTO object, with list of topics matching the language id filled in Language DTO only.
as you are newbie, just want to tell you that when you are using hibernate, your DTO and table design should be very precise.

Related

How to fill outer and inner classes

import lombok.Data;
#Data
public class P {
private String name;
private String operationId;
#Data
public static class Operation {
private Timestamp ts;
private String id;
}
}
I want to fill in the fields of both classes with one query using join
<select id="getAllP" resultType="com.mappers.P">
SELECT t."name" as name, t."operationId" as operationId, o."ts" as ts, o."id" as id
FROM FROM "P" t JOIN "Operation" o ON t."operationId" = o."id"
</select>
If leave resultType = "com.mappers.P" in this form, then only the field of the outer class is filled, if resultType = "com.mappers.P & Operation" then only the internal one, how to ensure that the fields of both classes are filled? thanks in advance
Something like this, although you can go deeper by splitting them into two result maps and building them that way. Use the documentation above, and you can get super creative.
<resultMap id="pMap">
<result property="name" column="name"/>
<association type="Operation" columnPrefix="o_">
<result property="name" column="name"/>
<result property="ts" column="name"/>
</association>
</resultMap>
<select id="getAllP" resultMap="pMap">
...
</select>

How to properly map MyBatis parent relation?

I'm currently struggling on trying to map my class using MyBatis and Oracle database.
I have a table with columns ID, CODE, DESCRIPTION, PARENT, VALUE, UNITS
I have a model:
public class Parameter {
private String code;
private String description;
private String value;
private Parameter parent;
private String units;
<...>
}
and a xml mapper parameterMapper.xml:
<resultMap id="ParameterResultMap" type="Parameter">
<id property="id" column="id" />
<result property="code" column="code" />
<result property="description" column="description" />
<result property="value" column="value" />
<result property="units" column="units" />
<association property="parent"
resultMap="persistence.ParameterMapper.ParameterResultMap"
columnPrefix="parent_" />
</resultMap>
<select id="readParameter" parameterType="Parameter" resultMap="ParameterResultMap">
SELECT
p.id,
p.code,
p.description,
p.value,
p.units,
par.id AS parent_id,
par.code AS parent_code,
par.description AS parent_description,
par.value AS parent_value,
par.units AS parent_units
FROM parameter p
LEFT JOIN parameter par ON par.code = p.parent
</select>
And I get java.lang.IllegalArgumentException: Result Maps collection does not contain value for persistence.ParameterMapper.ParameterResultMap] exception on my console.
What I've done wrong and where could be the problem?
Thanks in advance!

JPA | One to one mapping with eager fetch

I am working with a JPA sample application with hibernate as JPA provider.
In which I am having a one to one mapping with fetch type EAGER.
Here below is my code block:
Person.java
public class Person extends Persistent implements Serializable {
private static final long serialVersionUID = 1L;
String name;
// address as composition
Address address;
String addressId;
Address.java
public class Address extends Persistent implements Serializable {
private static final long serialVersionUID = 1L;
String address;
mapping.xml
<entity name="Person" class="com.csam.mtp.wallet.wallet.Person"
access="FIELD">
<table name="person" />
<attributes>
<basic name="name">
<column name="name" length="36" nullable="false" />
</basic>
<basic name="addressid">
<column name="addressid" length="36"/>
</basic>
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" insertable="false" updatable="false"
nullable="false" />
</one-to-one>
</attributes>
</entity>
<entity name="address" class="com.csam.mtp.wallet.wallet.Address"
access="FIELD">
<table name="address" />
<attributes>
<basic name="address">
<column name="address" length="36" />
</basic>
</attributes>
</entity>
DB Script
CREATE TABLE address (
id VARCHAR (50) not null,
address VARCHAR(50),
primary key(id)
);
CREATE TABLE person (
id VARCHAR (50) not null,
name VARCHAR(50),
addressid VARCHAR(50),
primary key(id),
foreign key (addressid) REFERENCES address(id),
);
Driver class
Address address = new Address();
address.setAddress("test");
address = addressRepository.save(address);
Person person = new Person("Mike");
//person.setAddressId(address.id());
try {
person = personRepository.save(person);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(personRepository.exists(person.getID()));
Person savedPerson = personRepository.findOne(person.getID());
// Asserts
assertNotNull(savedPerson);
assertNotNull(savedPerson.id());
While saving person If i don't set address id then it saves person successfully, exists returns true but find one api always returns null
while saving person if first I save address and set address id in person and save it then
It saves person successfully, exists return true and find one api return Person object with address object (as it is eager)
So here below is my query,
Is it in case of one to one mapping with eager fetch on child, child must be saved and set in parent before the saving of parent?
I tried to find out related arcticle but not able to get one, Please assist.
Thanks in advance
In your case the Address is the parent and the Person is the Child. That's because the Child points to the Parent through the addressId FK.
Because you don't have a bi-directional association, you shouldn't have insertable="false" updatable="false"
With these settings, Hibernate won't be able to include the addressId in the INSERT or the UPDATE query.
Remove insertable="false" updatable="false"
Remove the addressId column too, since you can reference it through address.id
Add cascade from Person to Address:
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" nullable="false" />
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
</one-to-one>
Probably it would be better to have a bi-directional association so that an Address has also a reference to a Person and in that case you should probably have a one-to-many association, since an Address may have multiple Persons.
Finally got the solution with nullable=true, as below:
<one-to-one name="address" fetch="EAGER">
<join-column name="addressid"
referenced-column-name="id" insertable="false" updatable="false"
nullable="true" />
</one-to-one>

How to Improve Performance when Persisting Large Collections using Spring EntityManager Hibernate

I am using Spring/Hibernate done the JPA way using org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean and configured using spring xml, persistence.xml and JPA 2 annotations.
Functionally it is fine and persisting correctly. However, I have a requirement to store entity A which has a bidirectional OneToMany with a large collection of B as quickly as possible.
I am using various options in persistence.xml to try to speed up inserts and reduce memory use (the application writes about as much as it reads)
<property name="hibernate.id.new_generator_mappings" value="true" />
<property name="hibernate.jdbc.batch_size" value="50" />
<property name="hibernate.order_inserts" value="true" />
<property name="hibernate.order_updates" value="true" />
<property name="hibernate.cache.use_query_cache" value="false" />
<property name="hibernate.cache.use_second_level_cache" value="false" />
and the persist is done using
entityManager.persist(instanceOfA)
Edit Additional info:
Each entity has a generated id like this:
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO, generator="SEQUENCE_GENERATOR")
#SequenceGenerator(name="SEQUENCE_GENERATOR", sequenceName="MY_SEQUENCE", allocationSize=50)
private Long id;
which relates to an Oracle sequence
CREATE SEQUENCE MY_SEQUENCE MINVALUE 1 MAXVALUE 999999999999999999999999999 START WITH 1 INCREMENT BY 50 NOCYCLE NOCACHE NOORDER;
When I run the code with show sql on I can see lots of insert statements taking quite a while.
I have read that I need to call entityManager.flush(); entityManager.clear(); every 50 rows inserted.
http://abramsm.wordpress.com/2008/04/23/hibernate-batch-processing-why-you-may-not-be-using-it-even-if-you-think-you-are/
Does this mean that I need to break up the persist into
entityManager.persist(instanceOfA);
instanceOfA.addB(instanceOfB);
entityManager.persist(instanceofB);
adding a flush clear every 50 calls to persist()?
Is there a cleaner way of doing it? (my actual object hierarchy has about 7 layers of relations like A and B)
I was thinking about using JDBC for the inserts, but I hate writing row mappers :)
I have heard about org.hibernate.StatelessSession but there is no method of getting that from a JPA entity manager without casting to SessionFactory at some point - again not very clean.
Thanks in advance!
I faced the same problem in one of my projects. I was using Hibernate with MySQL backend with an identity ID generator. The issue with that is, Hibernate needs to hit the database once for each entity saved to actually get an ID for it. I switched to the increment generator and saw immediate benefits (all inserts got batched).
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Column(name = "id", nullable = false)
private long id;
The increment generator generates IDs in-memory and does not need to hit the database. I am guessing that the sequence generator also needs to hit the database as it is defined in the database. The con of using increment is, Hibernate should have exclusive insert access to the database and it may fail in a clustered setup.
Another trick I used was to append rewriteBatchedStatements=true to the JDBC URL. This is MySQL specific but I think there may be a similar directive for Oracle.
And that "call flush after every n inserts" trick works too. Here is a sample code to do that (using google-guava classes):
public List<T> saveInBatches(final Iterable<? extends T> entities, final int batchSize) {
return ImmutableList.copyOf(
Iterables.concat(
Iterables.transform(
Iterables.partition(entities, batchSize),
new Function<List<? extends T>, Iterable<? extends T>>() {
#Override
public Iterable<? extends T> apply(final List<? extends T> input) {
List<T> saved = save(input); flush(); return saved;
}})));
}
public List<T> save(Iterable<? extends T> entities) {
List<T> result = new ArrayList<T>();
for (T entity : entities) {
entityManager.persist(entity);
result.add(entity);
}
return result;
}
Use pure JDBC for bulk/large insert. Don't use any ORM Framework for it.

How to use existing Oracle sequence to generate id in hibernate?

I have legacy Oracle db with a sequence named PRODUCT_ID_SEQ.
Here is the mapping of Product class for which I need generate correct ids:
public class Product {
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "retailerRaw_seq")
#SequenceGenerator(name = "retailerRaw_seq",
sequenceName = "PRODUCT_ID_SEQ")
private Long id;
...
}
But looks like ids are generated with an interval of 50, like 1000, 1050, 1100 etc. This corresponds to the default value of allocationSize property = 50. So that means that Hibernate doesn't actually use the sequence which is already defined in the db.
How do I make Hibernate use the sequence?
The answer to the original question:
#SequenceGenerator(name="EL_SEQ", sequenceName="EL_SEQ",allocationSize=1)
It is allocationSize that sets the value to increment by.
I'm not used to use annotations, this is what I have in my *.hbm.xml:
<id name="id" type="java.lang.Integer">
<column name="ID_PRODUCT" />
<generator class="sequence-identity" >
<param name="sequence">PRODUCT_ID_SEQ</param>
</generator>
</id>
You can easily map this to annotations. The generator sequence-identity uses auto increment with sequences.
Here is a working example with annotations, this way, the existing DB sequence will be used (you can also use the "sequence" strategy, but with less performance on insertion) :
#Entity
#Table(name = "USER")
public class User {
// (...)
#GenericGenerator(name = "generator", strategy = "sequence-identity", parameters = #Parameter(name = "sequence", value = "USER_SEQ"))
#Id
#GeneratedValue(generator = "generator")
#Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
public Long getId() {
return this.id;
}
I had the same issue while upgrading from 3.5.5 to 5.0.6.Final.
I solved it by re-configuring mapping in the HBM file from:
<generator class="sequence">
<param name="sequence">PRODUCT_ID_SEQ</param>
</generator>
to:
<generator class="org.hibernate.id.enhanced.SequenceStyleGenerator">
<param name="prefer_sequence_per_entity">true</param>
<param name="optimizer">none</param>
<param name="increment_size">1</param>
<param name="sequence_name">PRODUCT_ID_SEQ</param>
</generator>
Create your sequence name in Oracle, for example, contacts_seq.
In Your POJO Class . Define the following annotation for your sequence.
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="my_seq_gen")
#SequenceGenerator(name="my_seq_gen", sequenceName="contacts_seq")
If you use javax.persistence.SequenceGenerator, hibernate use hilo and will possibly create large gaps in the sequence. There is a post addressing this problem: https://forum.hibernate.org/viewtopic.php?t=973682
there are two ways to fix this problem
In the SequenceGenerator annotation, add allocationSize = 1, initialValue= 1
instead of using javax.persistence.SequenceGenerator, use org.hibernate.annotations, like this:
#javax.persistence.SequenceGenerator(name = "Question_id_sequence", sequenceName = "S_QUESTION")
#org.hibernate.annotations.GenericGenerator(name="Question_id_sequence", strategy = "sequence", parameters = { #Parameter(name="sequence", value="S_QUESTION") } )
I have tested both ways, which works just fine.
allocationSize and incrementBy are completely different things.
Hibernate is of course using your sequence created in DB but depending on allocationSize you might find gap in generated value.
For example-
Let assume current sequence value is 5, increment by 1 in db, and allocationSize default 50.
Now you want to save a collection of 3 element through hibernate, then
Hibernate will assign generated id 250, 251, 252
This is for optimization purpose. Hibernate doesn't have to go back to db and fetch next incremented value.
If you don't want this just setting allocationSize = 1 as already answered will do the purpose
I use following on PostgreSQL and works just fine.
#Id
#GeneratedValue(generator = "my_gen")
#SequenceGenerator(name = "my_gen", sequenceName = "my_seq_in_db")
private int userId;
By default Hibernate uses sequence HiLo generator which ,unless you have special needs, it is good (performance wise). You can read more of that in my blog here
Eyal
First : you should create in your database the sequence like:
CREATE SEQUENCE "PRODUCT_ID_SEQ" MINVALUE 0 MAXVALUE 1000000000 INCREMENT BY 1 START WITH 1 CACHE 500 NOORDER NOCYCLE ;
and in your file Product.hbm.xml configuration make :
<class name="ProductPersistant" table="Product">
<id name="id" type="java.lang.Long" column="productID" >
<generator class="sequence">
<param name="sequence">PRODUCT_ID_SEQ</param>
</generator>
</id>

Resources