I have some class Calls
public class Calls {
private String Field1;
//and many fields, for example 15
private List<MyModel> models;
}
Each minute I get a List of Calls
List<Calls> list = someService.getCallsList();
and try to insert it into DB
Session session = getSession();
Transaction tx = session.beginTransaction();
for(int i=0; i< list.size(); i++) {
Calls calls = list.get(i);
session.createSQLQuery(
"INSERT /*+ ignore_row_on_dupkey_index(CALLS,UNIQUE_CALLS_CONSTRAINT) */ " +
"INTO CALLS(Field1,....,FieldEnd) VALUES(:field1,...,:fieldEnd)")
.setParameter("field1",calls.getField1()
//set all params
.setParameter("fielEnd1",calls.getFieldEnd();
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
}
I need to INSERT row and skip all duplicate: /*+ ignore_row_on_dupkey_index(CALLS,UNIQUE_CALLS_CONSTRAINT) *// For it I use SQLQuery
My question is: how can I make this using hibernate without SQLQuery? as generally is implemented correctly. I have 15 parameters and I do not want them all to register for SQLQuery
You can use Oracle hint by Criteria,see:
https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/Criteria.html#addQueryHint(java.lang.String)
Related
I'm trying, using Hibernate 4.3 and SQL-Server 2014, to perform batch insert into a table for only the entities that are not already stored.
I have created a simple table with a primary key definded to ignore duplicate keys
create table items
(
itemid uniqueidentifier not null,
itemname nvarchar(30) not null,
)
alter table items add constraint items_pk primary key ( itemid ) with ( ignore_dup_key = on );
Trying to perform a batch insert through the StatelessSession insert method, the batch insert may fail if one or more entities are already stored into the database table: Hibernate throws a StaleStateException:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:63)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3581)
at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:144)
at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:123)
at it.test.testingestion.HibernateStatelessSessionPersisterImpl.persistData(HibernateStatelessSessionPersisterImpl.java:18)
at it.test.testingestion.App.main(App.java:76)
When the batch statements are completed Hibernate performs a check over the returned rows count that is different than the expected due to the ignore duplicate keys.
With JDBC, performing a batch insert using a prepared statement, the entities that are already stored into the destination table are skipped but the new entities are saved correctly.
How is it possible to configure Hibernate to perform batch insert ignoring the existing data or wihout performing a check of the affected rows?
Thanks a lot
Update #1
As workaround, to force the number of rows affected even if a duplicated insert occurs, I have created the following Hibernate Interceptor:
public class CustomInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = -8022068618978801796L;
private String getTemporaryTableName() {
String currentThreadName = Thread.currentThread().getName();
return "##" + currentThreadName.replaceAll("[^A-Za-z0-9\\_]", "");
}
private void createTemporaryTable(Connection connection) {
String tempTableName = this.getTemporaryTableName();
String commandText = String.format("if (object_id('tempdb.dbo.%s') is null) begin create table [%s] ( dummyfield int ); insert into %s ( dummyfield ) values ( 0 ) end ", tempTableName, tempTableName, tempTableName);
try (PreparedStatement statement = connection.prepareStatement(commandText)) {
statement.execute();
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(String.format("An error has been occurred trying to create the temporary table %s", tempTableName), e);
}
}
public CustomInterceptor(Connection connection) {
this.createTemporaryTable(connection);
}
#Override
public String onPrepareStatement(String sql) {
int ps = sql.toLowerCase().indexOf("insert into ");
if (ps == 0) {
String tableName = this.getTemporaryTableName();
return sql + "; if (##rowcount = 0) update [" + tableName + "] set dummyfield = 1";
}
return super.onPrepareStatement(sql);
}
}
The interceptor, when a new instance is created, creates a new temporary table inserting a new record.
When an insert statement is intercepted, an update of the record saved into the instanced temporary table is performed if no rows has been affected by the insert statement: this tricks Hibernate about the returned rows event if a duplicate entity is inserted and no StatelessSessionImpl exception is thrown.
Obviously the downside of the trick is the cost to perform the extra update for each row that is not inseted into the table.
Does anyone knows a better way, that does not affect the insert performances, to insert entities into a table thats ignore the duplicate entries using Hibernate?
Thanks
For better performance, I would prefer using JDBCBatchUpdate
Approach 1:
As you are filtering the new records, the number of records will not be a constraint. So you could specify an association mapping in the entity layer and could perform a Hibernate batch insert or JDBC Batch update.
Approach 2:
Using Native SQl Query
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
//get Connction from Session
session.doWork(new Work() {
#Override
public void execute(Connection conn) throws SQLException {
PreparedStatement pstmt = null;
try{
String sqlInsert = "insert into tbl(name) values (?) ";
pstmt = conn.prepareStatement(sqlInsert );
int i=0;
for(String name : list){
pstmt .setString(1, name);
pstmt .addBatch();
//20 : JDBC batch size
if ( i % 20 == 0 ) {
pstmt .executeBatch();
}
i++;
}
pstmt .executeBatch();
}
finally{
pstmt .close();
}
}
});
tx.commit();
session.close();
I have about 500.000 rows of data to insert into one table.
I am currently inserting them one at a time (I know it's bad) like this :
Dao method :
public static final String SET_DATA = "insert into TABLE (D_ID, N_ID, VALUE, RUN_ID) " + "values (?, ?, ?, ?)";
public void setData(String dId, String nId, BigDecimal value, Run run) throws HibernateException {
if (session == null) {
session = sessionFactory.openSession();
}
SQLQuery select = session.createSQLQuery(SET_DATA);
select.setString(0, dId);
select.setString(1, nId);
select.setBigDecimal(2, value);
select.setLong(3, run.getRunId());
select.executeUpdate();
}
How can I do this more efficiently ?
Why you went for hand written SQL query ?? If you are writing sql in such a way , you are definitely not getting the fruits of hibernate.
Learn Batch Insert Example code for Batch Insert,
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
The fastest way is always using the native bulk import tool of your DBMS.
Do not use hibernate or java for that.
Dump the data into some format your DB understands (most probably in the same file system of your DB) and use your DBMS native import tool.
Ideally you should use batch Insert. Refer to example provided here. This inserts multiple records in DB in a single go.
dbConnection.setAutoCommit(false);//commit trasaction manually
String insertTableSQL = "INSERT INTO DBUSER"
+ "(USER_ID, USERNAME, CREATED_BY, CREATED_DATE) VALUES"
+ "(?,?,?,?)";
PreparedStatement = dbConnection.prepareStatement(insertTableSQL);
for(int i=0;i<500000;i++){
preparedStatement.setInt(1, 101);
preparedStatement.setString(2, "mkyong101");
preparedStatement.setString(3, "system");
preparedStatement.setTimestamp(4, getCurrentTimeStamp());
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
dbConnection.commit();
1.Solution
StringBuilder sb = new StringBuilder();
sb.AppendLine("insert into Table_Name (column1, column1, column1 , column1 ) values ");
foreach (var item in req)
{
sb.AppendFormat("({0},{1},{2},'{3}'),",
item.val1, item.val2, item.val3, item.val4);
}
sb = sb.Remove(sb.Length - 1, 1);
ExecuteNonQuery(sb.ToString());
return true;
if record count great then 1000
StringBuilder sb = new StringBuilder();
foreach (var item in req)
{
sb.AppendLine("insert into Table_Name(column1, column1, column1 , column1) values ");
sb.AppendFormat("({0},{1},{2},'{3}') ;",
item.val1, item.val2, item.val3, item.val4);
}
sb = sb.Remove(sb.Length - 1, 1);
ExecuteNonQuery(sb.ToString());
return true;
I have tried a lot to update my table using hql but i didn't find the solution , i have searched on internet too, I am new in java and hibernate please help me to find the solution.
my code is written below.
session.getTransaction().begin();
Query query = session.createQuery("update DocDetail set DocName = :docname" +
" where Id = :docId");
query.setParameter("docname", "Jack");
query.setParameter("docId", 3);
int result = query.executeUpdate();
session.getTransaction().commit();
but I got the following error.
Exception in thread "AWT-EventQueue-0" org.hibernate.QueryException: query must begin with SELECT or FROM: update [update clinic.entity.DocDetail set DocName = :studentName where Id = :studentId]
at org.hibernate.hql.classic.ClauseParser.token(ClauseParser.java:106)
at org.hibernate.hql.classic.PreprocessingParser.token(PreprocessingParser.java:131)
at org.hibernate.hql.classic.ParserHelper.parse(ParserHelper.java:51)
If you are using hibernate, you should try to access entities not tables.
The biggest advantage of hibernate is that it provides you ORM (object relational mapping).
Here is the example how to update an entity with hibernate
(of course corresponding table is also updated).
/* Method to UPDATE salary for an employee */
public void updateEmployee(Integer EmployeeID, int salary ){
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
Employee employee =
(Employee)session.get(Employee.class, EmployeeID);
employee.setSalary( salary );
session.update(employee);
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
You are creating a Native(SQL) query using createQuery() method instead of createSQLQuery() method so just change your code as follows
session.getTransaction().begin();
Query query = session.createSQLQuery(
"update DocDetail set DocName = :docname" + " where Id = :docId");
query.setParameter("docname", "Jack");
query.setParameter("docId", 3);
int result = query.executeUpdate();
session.getTransaction().commit();
read about about this in detail:
Different ways to create query
Difference between createQuery and createSQLQuery
hope this will solve your problem
To update object without SQL or HQL you can use next code snippet.
Session sess = factory.openSession();
Transaction tx;
try {
tx = sess.beginTransaction();
sess.update(yourObject);
tx.commit();
}
catch (Exception e) {
if (tx!=null) tx.rollback();
throw e;
}
finally {
sess.close();
}
Read documentation about update - possible you have to use merge or saveOrUpdate.
Here a way of updating data into table using hibernate hql:
Configuration cfg = new Configuration();
cfg.configure("HibernateService/hibernate.cfg.xml");
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction t = session.beginTransaction();
String hql = "UPDATE Userreg SET uname = :uname, uemail = :uemail, uphone = :uphone WHERE uemail = :uemail";
Query query = session.createQuery(hql);
query.setParameter("uname", uname);
query.setParameter("uemail", uemail);
query.setParameter("uphone", uphone);
int rr = query.executeUpdate();
t.commit();
if (rr != 0) {
return true;
} else {
return true;
}
you can use hibernate session's merge.
such as
User user = session.find("1");
//get Persistence entity``String userName = user.getUserName(); // userName = "enzo"
//user.setUserName("leo");
session.merge(user);
// Test entity user's useName
String userNameNew = session.find("1").getUserName; // now userName is "leo"
I hope can help you;
Here is the code example from Hibernate about batch processing:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
At the beginning of codes, it uses openSession().However, when i wrote my code, i use getCurreentSession(). It seems like it will generate org.hibernate.TransactionException: nested transactions not supported error.
Could anybody explain why this happens?
SessionFactory.openSession() always opens a new session that you have to close once you are done with the operations. SessionFactory.getCurrentSession() returns a session bound to a context - you don't need to close this.
Why is this giving me a lock timeout:
for(int i = 0; i < playersCount ; i++) {
StatUser stats = (StatUser) selectedUsers.get(i).getStatUsers().iterator().next();
int gc = stats.getGamesPlayed();
int gm = stats.getMakeCount();
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction tx = session.beginTransaction();
if(i == winnerIndex) {
stats.setGamesPlayed(gc++);
stats.setMakeCount(gm++);
session.update(stats);
session.flush();
tx.commit();
customTextBean.sendMail(selectedUsers.get(i).getEmail(), "Tillykke du har vundet");
}
else {
stats.setGamesPlayed(gc++);
session.update(stats);
session.flush();
tx.commit();
customTextBean.sendMail(selectedUsers.get(i).getEmail(), "Tillykke " + winnersName + " skal lave kaffe");
}
}
If you create a new transaction (session.beginTransaction();), then a new DB connection is created. So you have a transaction which has a read-lock on stats (from the for loop) and inside of that you try to write to the same row -> Deadlock.
To fix that, you first fetch all StatUsers with a second loop, close the first transaction and then iterate over the result in the code above. If you can't do that because you run out of memory, then Hibernate is no longer your friend and you must use custom SQL.
Other solutions: Use optimistic locking or read the data to be changed with custom SQL and instantiate the objects in the loop.