I'm getting the above error when I'm trying to update sphinx real time indexes. Since sphinx uses a database very close to mysql, I'm trying to use entity manager to update indexes. But sphinxql isn't a full mysql database so if I use entitymanager.merge method the sql generated isn't understood by sphinxql. Therefore, I decided to use createNativeQuery to insert new indices or delete indices from the Real Time Index. However, I'm getting the exception in the title when I invoke .executeUpdate method of createNativeQuery.
This is my Dao:
public interface RTIndexGeoDao {
public boolean deleteIndex(long id);
public boolean insertIndex(long id, long agentActivityId, long agentId, double latitude, double longitude);
}
This is my Implementation of Dao:
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
#Repository(value = "rtIndexGeoDao")
public class JPARTIndexDao implements RTIndexGeoDao {
private EntityManager em;
#PersistenceContext(unitName = "rtIndexPU")
public void setRtIndexEntityManager(EntityManager em)
{
this.em = em;
}
public boolean deleteIndex(long id) {
//This is to update our geodistance index
try
{
this.em.createNativeQuery("delete from rt_geo where agent_agent_id = ?1").setParameter(1, id).executeUpdate();
return true;
}
catch (Exception ex)
{
return false;
}
}
public boolean insertIndex(long id, long agentActivityId, long agentId,
double latitude, double longitude) {
try
{
this.em.createNativeQuery("insert into rt_geo(id, agent_activity_id, agent_agent_id, latitude, longitude) values(?1, ?2, ?3, ?4, ?5)")
.setParameter(1, id)
.setParameter(2, agentActivityId)
.setParameter(3, agentId)
.setParameter(4, latitude)
.setParameter(5, longitude).executeUpdate();
return true;
}
catch (Exception ex)
{
return false;
}
}
}
This is my RTIndexManager:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springtest.mavenspringapp.repository.RTIndexGeoDao;
#Component
#Transactional
public class SphinxRTIndexManagerImpl implements SphinxRTIndexManager {
/**
*
*/
private static final long serialVersionUID = 1L;
#Autowired
private RTIndexGeoDao rtIndexGeoDao;
public boolean deleteIndex(long id) {
return rtIndexGeoDao.deleteIndex(id);
}
public boolean insertIndex(long id, long agentActivityId, long agentId,
double latitude, double longitude) {
return rtIndexGeoDao.insertIndex(id, agentActivityId, agentId, latitude, longitude);
}
}
This is my persistence.xml
<persistence-unit name="rtIndexPU" transaction-type="RESOURCE_LOCAL">
</persistence-unit>
This is the controller:
#Autowired
private SphinxRTIndexManager rtIndexManager;
private AgentActivity createActivity(Agent agent, double latitude, double longitude, String agentActivityDescription)
{
//Steps to update indexes
boolean deleteEntry = rtIndexManager.deleteIndex(agent.getAgentId());
if (!deleteEntry) return null;
UPDATE: I'm posting my applicationContext.xml
<!-- enabling annotation driven configuration /-->
<context:annotation-config/>
<bean id="rtIndexEntityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="rtIndexDataSource"
p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
<property name="persistenceUnitName" value="rtIndexPU"></property>
</bean>
<bean id="rtIndexTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="rtIndexEntityManagerFactory"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
I have another persistence unit and every manager, dao, etc works. But when I use createNativeQuery.executeUpdate on entitymanager whose persistenceContext is rtIndexPU, this exception occurs:
javax.persistence.TransactionRequiredException: Executing an update/delete query
I've searched at every question available on the internet, but cannot have this issue solved. Thanks in advance.
Since you have two transaction managers in your code, the problem is most likely that your SphinxRTIndexManagerImpl is not using the correct one (or any).
You should change the code to:
#Component
#Transactional("rtIndexTransactionManager")
public class SphinxRTIndexManagerImpl implements SphinxRTIndexManager {
}
Also you have to configure the second transaction manager (rtIndexTransactionManager) for working with annotations, by doing:
<tx:annotation-driven transaction-manager="rtIndexTransactionManager"/>
Related
I am using Hibernate 2.1 on a Wildfly (JBoss) 10 to fetch an entity from the Database. Here is the model of the entity:
#Entity
#Table(name = "account")
#NamedQuery(name = "Account.findAll", query = "SELECT a FROM Account a")
public class Account implements Serializable {
private int id;
private double balance;
private double bonus;
#Id
public int getId() {
return this.id;
}
// Rest of the fields/setters/getters ommited
}
I have implemented a REST API call that updates the balance of a given account. The account is first retrieved from the database, validated, then the balance is updated through a separate bean-managed transaction in MySQL. After this completes I am asked to do some other operation on the account, on which I need the (new) updated balance. Find code attached below:
#Stateless
#Path("/financial")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class FinancialApi {
#Inject private AccountBean accountBean;
#Inject private AccountBalanceBean accountBalanceBean;
#POST
#Path("/balance/add")
public Response addBalance(TransactionProperties properties) {
Account account;
try {
account = accountBean.get(properties.getAccountId());
validateAccount(account);
} catch (ValidationException ex) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
// Update balance
accountBalanceBean.updateBalance(account.getId(), properties.getAmount());
// Refresh from DB to retrieve updated balance
accountBean.refresh(account);
// Do stuff with refreshed account data here
return Response.ok().build();
}
}
The "DAO" EJB handling the account
#Stateless
#LocalBean
public class AccountEJBean {
#PersistenceContext(unitName = "my-unit-name")
private EntityManager em;
public Account get(int id) {
try {
return (Account) em.createQuery("SELECT a FROM Account a WHERE a.id = :id")
.setParameter("id", id)
.setMaxResults(1)
.getSingleResult();
} catch (NoResultException ex) {
return null;
}
}
public void refresh(Account account) {
em.refresh(account);
}
}
and a (severely reduced) sample of the EJB that uses bean-managed transactions to update account balance:
#Stateless
#LocalBean
#TransactionManagement(TransactionManagementType.BEAN)
public class AccountBalanceEJBean {
#PersistenceContext(unitName = "my-unit-name")
private EntityManager em;
#Resource
private UserTransaction transaction;
public boolean updateBalance(int id, float price) {
try {
transaction.begin();
getUpdateQuery(id, price).executeUpdate();
transaction.commit();
} catch (Exception ex) {
transaction.rolloback();
}
}
}
and here is my persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence">
<persistence-unit name="my-unit-name" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/jboss/datasources/SQLDatasource</jta-data-source>
<class>mypackage.Account</class>
<properties>
<property name="hibernate.generate_statistics" value="false"/>
<property name="hibernate.connection.useUnicode" value="true"/>
<property name="hibernate.connection.characterEncoding" value="UTF-8"/>
</properties>
</persistence-unit>
</persistence>
This works perfectly fine until the part where I am trying to refresh account data from the database. At this point the account is refreshed with no issue on every case I have tried but the data of the account do not get updated. I am still retrieving the "stale" data that the account had when retrieved initially. After looking around for some time I figured it may be related to Hibernate caching mechanism. I have tried the following:
Detaching the entity on the get() method from the EntityManager and replacing refresh() with get()
Calling em.clear() and then retrieving the entity with a get() call again.
Attempting to clear the cache using em.getEntityManagerFactory().get().evictAll()
Setting hints "javax.persistence.cache.retrieveMode" and "javax.persistence.cache.storeMode" to BYPASS on the query of the get() method.
None of those seems to be working. I have tried updating the entity in my code and setting random values and every time the entity is refreshed in the initial retrieval state (not the one that is in the database, even if I alter different fields than balance not mentioned here).
From my understanding second-level cache is disabled by default on Hibernate and as far as I can tell that's not the case here. With this in my mind I move to the first-level cache but I can't seem to understand how to retrieve a Session (or if a session even exists here) from the entity manager in order to clear that cache which seems to be causing the problem. As a side note I am not sure of what's the point of having a first-level cache that doesn't get overriden by refresh() method and how this works in a distributed environment.
If that helps the Instance of the EntityManager that is injected is an instance of class org.jboss.as.jpa.container.TransactionScopedEntityManager.
Any ideas?
UPDATE: I have replicated the get() code in a second bean, let's call it AccountBean2 that's also injected into the API class. It seems to be working now. I am pretty confident it's a caching issue at this point but I am still not sure how to correct the initial issue so the question remains.
I have the following piece of code where I am using Spring's #Transactional annotation with JDBC template and it does not rollback a transaction. I have used random file names and table name. I am trying to delete a row for a Foreign key id and then insert a record for the same id in a database table named "data". But when I was testing I am seeing that if there is an error in the insert, the delete does not get rollbacked.
I am pretty new to Spring, so any help would be appreciated.
TestService.java
#Service
public class TestService {
#Autowired
TestRepository testRepository;
#Transactional(rollbackFor={Exception.class})
public void insertData(List<Data> dataList, Integer fkId)
throws Exception {
testRepository.updateData(dataList, fkId);
//do some other stuff
}
}
TestRepository.java
#Respository
public class TestRepository {
#Autowired
#Qualifier("dataJdbcTemplate")
private NamedParameterJdbcTemplate dataJdbcTemplate;
#Transactional(rollbackFor={Exception.class})
public void updateData(List<Data> dataList, Integer fkId)
throws Exception {
String deleteId = "DELETE FROM data where
fk_id = :fkId";
dataJdbcTemplate.update(deleteId, new
MapSqlParameterSource("fkId", fkId));
String sql = "INSERT INTO data(fk_id, column1, column2)"
+ " VALUES(:fkId, :column1, :column2)";
SqlParameterSource[] batch =
SqlParameterSourceUtils.createBatch(dataList.toArray());
dataJdbcTemplate.batchUpdate(sql, batch);
}
database.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="dataJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.
NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
You should try enabling transaction propagation.
You can read more here:
https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#tx-propagation
I have a JPA 2 web application (Struts 2, Hibernate 4 as JPA implementation only).
The current requirement is to add a (non-id) numeric sequential field, filled for certain rows only, to an existing entity. When inserting a new row, based on a certain condition, I need to set the new field to its highest value + 1 or to NULL.
For example:
ID NEW_FIELD DESCRIPTION
--------------------------------
1 1 bla bla
2 bla bla <--- unmatched: not needed here
3 bla bla <--- unmatched: not needed here
4 2 bla bla
5 3 bla bla
6 4 bla bla
7 bla bla <--- unmatched: not needed here
8 5 bla bla
9 bla bla <--- unmatched: not needed here
10 6 bla bla
In the good old SQL, it would be something like:
INSERT INTO myTable (
id,
new_field,
description
) VALUES (
myIdSequence.nextVal,
(CASE myCondition
WHEN true
THEN myNewFieldSequence.nextVal
ELSE NULL
END),
'Lorem Ipsum and so on....'
)
But I've no clue on how to achieve it with JPA 2.
I know I can define callbacks methods, but JSR-000317 Persistence Specification for Eval 2.0 Eval discourages some specific operations from inside it:
3.5 Entity Listeners and Callback Methods
- Lifecycle callbacks can invoke JNDI, JDBC, JMS, and enterprise beans.
- In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity
instances, or modify relationships within the same persistence
context.[43] A lifecycle callback method may modify the
non-relationship state of the entity on which it is invoked.
[43] The semantics of such operations may be standardized
in a future release of this specification.
Summarizing, yes to JDBC (!) and EJB, no to EntityManager and other Entities.
EDIT
I'm trying to achieve the solution described in the answer from #anttix, but I'm encoutering some problem, so please correct me where I'm wrong.
Table
MyTable
-------------------------
ID number (PK)
NEW_FIELD number
DESCRIPTION text
Main Entity
#Entity
#Table(name="MyTable")
public class MyEntity implements Serializable {
#Id
#SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id")
private Long id;
#OneToOne(cascade= CascadeType.PERSIST)
private FooSequence newField;
private String description
/* Getters and Setters */
}
Sub entity
#Entity
public class FooSequence {
#Id
#SequenceGenerator(name="seq_foo", sequenceName="seq_foo", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_foo")
private Long value;
/* Getter and Setter */
}
DAO
myEntity.setNewField(new FooSequence());
entityManager.persist(myEntity);
Exception
Caused by: javax.transaction.RollbackException: ARJUNA016053: Could not commit transaction.
[...]
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.postgresql.util.PSQLException: ERROR: relation "new_field" does not exist
What am I doing wrong ? I'm pretty new to JPA 2 and I've never used an entity not associated to a physical table... this approach is totally new to me.
I guess I need to put the #Column definition somewhere: how could JPA possibly know that the newField column (mapped through ImprovedNamingStrategy to new_field on the database) is retrieved through the value property of the FooSequence entity ?
Some pieces of the puzzle are missing.
EDIT
As asked in comments, this is the persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="MyService" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/myDS</jta-data-source>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy"/>
<property name="hibernate.query.substitutions"
value="true 'Y', false 'N'"/>
<property name="hibernate.show_sql" value="true" />
<property name="format_sql" value="true" />
<property name="use_sql_comments" value="true" />
</properties>
</persistence-unit>
</persistence>
One possible solution is to use a separate entity with its own table that will encapsulate only the new field and have an OneToOne mapping with that entity. You will then instantiate the new entity only when you encounter an object that needs the additional sequence number. You can then use any generator strategy to populate it.
#Entity
public class FooSequence {
#Id
#GeneratedValue(...)
private Long value;
}
#Entity
public class Whatever {
#OneToOne(...)
private FooSequnce newColumn;
}
See:
Hibernate JPA Sequence (non-Id)
https://forum.hibernate.org/viewtopic.php?p=2405140
A gradle 1.11 runnable SSCCE (using Spring Boot):
src/main/java/JpaMultikeyDemo.java
import java.util.List;
import javax.persistence.*;
import lombok.Data;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
#Configuration
#EnableTransactionManagement
#EnableAutoConfiguration
public class JpaMultikeyDemo {
#Entity #Data
public static class FooSequence {
#Id #GeneratedValue private Long value;
}
#Entity #Data
public static class FooEntity {
#Id #GeneratedValue private Long id;
#OneToOne private FooSequence sequence;
}
#PersistenceContext
EntityManager em;
#Transactional
public void runInserts() {
// Create ten objects, half with a sequence value
for(int i = 0; i < 10; i++) {
FooEntity e1 = new FooEntity();
if(i % 2 == 0) {
FooSequence s1 = new FooSequence();
em.persist(s1);
e1.setSequence(s1);
}
em.persist(e1);
}
}
public void showAll() {
String q = "SELECT e FROM JpaMultikeyDemo$FooEntity e";
for(FooEntity e: em.createQuery(q, FooEntity.class).getResultList())
System.out.println(e);
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JpaMultikeyDemo.class);
context.getBean(JpaMultikeyDemo.class).runInserts();
context.getBean(JpaMultikeyDemo.class).showAll();
context.close();
}
}
build.gradle
apply plugin: 'java'
defaultTasks 'execute'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-milestone" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-data-jpa:1.0.0.RC5"
compile "org.projectlombok:lombok:1.12.6"
compile "com.h2database:h2:1.3.175"
}
task execute(type:JavaExec) {
main = "JpaMultikeyDemo"
classpath = sourceSets.main.runtimeClasspath
}
See also: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-configure-datasource
This looks like it could be a good case for some AOP. First start by creating a custom field annotation #CustomSequenceGeneratedValue, and then annotate the field on the entity with it:
public class MyEntity {
...
#CustomSequenceGeneratedValue
private Long generatedValue;
public void setGeneratedValue(long generatedValue) {
}
}
Then an aspect is created to increment generated values:
#Aspect
public class CustomSequenceGeneratedValueAspect {
#PersistenceContext
private EntityManager em;
#Before("execution(* com.yourpackage.dao.SomeDao.*.*(..))")
public void beforeSaving(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
MethodSignature ms = (MethodSignature) jp.getSignature();
Method m = ms.getMethod();
Annotation[][] parameterAnnotations = m.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
for (Annotation annotation : annotations) {
if (annotation.annotationType() == CustomSequenceGeneratedEntity.class) {
... find generated properties run query and call setter ...
... Query query = em.createNativeQuery("select MY_SEQUENCE.NEXTVAL from dual");
}
}
}
}
}
Then the aspect is scanned with <aop:aspectj-autoproxy />, and applied to any Spring DAO saving entities of this type. The aspect would populate the sequence generated values based on a sequence, in a transparent way for the user.
You mentioned being open to using JDBC. Here is how you can you use Entity Callback with JdbcTemplate, the example uses Postgres's syntax for selecting next value in a sequence, just update it to use the right syntax for your DB.
Add this to your entity class:
#javax.persistence.EntityListeners(com.example.MyEntityListener.class)
And here is listener implementation (#Qualifier and required = true are necessary for it to work):
package com.example;
import javax.persistence.PostPersist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
#Component
public class MyEntityListener {
private static JdbcTemplate jdbcTemplate;
#Autowired(required = true)
#Qualifier("jdbcTemplate")
public void setJdbcTemplate(JdbcTemplate bean) {
jdbcTemplate = bean;
}
#PostPersist
#Transactional
public void postPersis(MyEntity entity) {
if(isUpdateNeeded(entity)) {
entity.setMyField(jdbcTemplate.queryForObject("select nextval('not_hibernate_sequence')", Long.class));
}
}
private boolean isUpdateNeeded(MyEntity entity) {
// TODO - implement logic to determine whether to do an update
return false;
}
}
The hacky solution I used to keep it simple is the following:
MyEntity myEntity = new MyEntity();
myEntity.setDescription("blabla");
em.persist(myEntity);
em.flush(myEntity);
myEntity.setNewField(getFooSequence());
The complete code ("pseudo-code", I've written it directly on SO so it could have typos) with transaction handling would be like :
Entity
#Entity
#Table(name="MyTable")
public class MyEntity implements Serializable {
#Id
#SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id")
private Long id;
private Long newField; // the optional sequence
private String description
/* Getters and Setters */
}
Main EJB:
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER) // default
public class MainEjb implements MainEjbLocalInterface {
#Inject
DaoEjbLocalInterface dao;
// Create new session, no OSIV here
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Long insertMyEntity(boolean myCondition) throws Exception {
try {
MyEntity myEntity = dao.insertMyEntity();
// if this break, no FooSequence will be generated
doOtherStuff();
// Do other non-database stuff that can break here.
// If they break, no FooSequence will be generated,
// and no myEntity will be persisted.
if (myCondition) {
myEntity.setNewField(dao.getFooSequence());
// This can't break (it would have break before).
// But even if it breaks, no FooSequence will be generated,
// and no myEntity will be persisted.
}
} catch (Exception e){
getContext().setRollbackOnly();
log.error(e.getMessage(),e);
throw new MyException(e);
}
}
}
DAO EJB
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER) // default
public class DaoEjb implements DaoEjbLocalInterface {
#PersistenceContext( unitName="myPersistenceUnit")
EntityManager em;
// default, use caller (MainEJB) session
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public MyEntity insertMyEntity() throws Exception{
MyEntity myEntity = new MyEntity();
myEntity.setDescription("blabla");
em.persist(myEntity);
em.flush(); // here it will break in case of database errors,
// eg. description value too long for the column.
// Not yet committed, but already "tested".
return myEntity;
}
// default, use caller (MainEJB) session
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public Long getFooSequence() throws Exception {
Query query = em.createNativeQuery("SELECT nextval('seq_foo')");
return ((BigInteger) query.getResultList().get(0)).longValue();
}
}
This will guarantee there will be no gaps in the FooSequence generation.
The only drawback, that I don't care at all in my use case, is that FooSequence and the #Id sequence are not synchronized, so two concurrent inserts may have "inverted" FooSequence values, respecto to their order of arrive, eg.
ID NEW FIELD
-------------
1 2
2 1
I'm having problems trying to use batchUpdate, from Spring's JdbcTemplate.
The problem is that i want to execute two SQL operations: a DELETE method (to clear my table) and then an INSERT method. It works fine the first time i make the call (from jsp). But from the second attempt on, when i try to do the call, the DELETE procedure isn't called or executed, just the INSERT procedure, causing an unique constraint exception.
First i tried this:
public class MyTableDAOStoredProcedure extends JdbcDaoSupport implements MyTableDAO {
...
....
public void insert(final List<MyObject> myObjectList) {
...
String deleteSql = "DELETE FROM ......";
String insertSql = "INSERT INTO ......";
// Delete Procedure
jdbcTemplate.execute(deleteSql);
//Insert Procedure
jdbcTemplate.batchUpdate(insertSql, new BatchPreparedStatementSetter() {
#Override
public int getBatchSize() {
return myObjectList.size();
}
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
MyObject object = myObjectList.get(i);
ps.setString(1, myObject.getA());
ps.setInt(2, myObject.getB());
}
});
}
}
Then i tried this:
public class MyTableDAOStoredProcedure extends JdbcDaoSupport implements MyTableDAO {
...
...
String deleteSql = "DELETE FROM MY OBJECT";
String insertsql = "INSERT INTO MY_OBJECT values(1,2)";
jdbcTemplate.batchUpdate(new String[] { deleteSql, insertSql});
}
I think it might be some Spring Transaction problem. Here it is the config of my DAO procedure on applicationContext.xml, it's quite simple:
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
.
.
.
<bean id="myTableDAOStoredProcedure" class="....dao.spring.MyTableDAOStoredProcedure">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
Any ideas or suggestions?
For an answer scroll down to the end of this...
The basic problem is the same as asked multiple time. I have a simple program with two POJOs Event and User - where a user can have multiple events.
#Entity
#Table
public class Event {
private Long id;
private String name;
private User user;
#Column
#Id
#GeneratedValue
public Long getId() {return id;}
public void setId(Long id) { this.id = id; }
#Column
public String getName() {return name;}
public void setName(String name) {this.name = name;}
#ManyToOne
#JoinColumn(name="user_id")
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
}
The User:
#Entity
#Table
public class User {
private Long id;
private String name;
private List<Event> events;
#Column
#Id
#GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column
public String getName() { return name; }
public void setName(String name) { this.name = name; }
#OneToMany(mappedBy="user", fetch=FetchType.LAZY)
public List<Event> getEvents() { return events; }
public void setEvents(List<Event> events) { this.events = events; }
}
Note: This is a sample project. I really want to use Lazy fetching here.
Now we need to configure spring and hibernate and have a simple basic-db.xml for loading:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" scope="thread">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" />
<property name="username" value="root" />
<property name="password" value="" />
<aop:scoped-proxy/>
</bean>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread">
<property name="dataSource" ref="myDataSource" />
<property name="annotatedClasses">
<list>
<value>data.model.User</value>
<value>data.model.Event</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
<aop:scoped-proxy/>
</bean>
<bean id="myUserDAO" class="data.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="myEventDAO" class="data.dao.impl.EventDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
</beans>
Note: I played around with the CustomScopeConfigurer and SimpleThreadScope, but that didnt change anything.
I have a simple dao-impl (only pasting the userDao - the EventDao is pretty much the same - except with out the "listWith" function:
public class UserDaoImpl implements UserDao{
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
#SuppressWarnings("unchecked")
#Override
public List listUser() {
return hibernateTemplate.find("from User");
}
#Override
public void saveUser(User user) {
hibernateTemplate.saveOrUpdate(user);
}
#Override
public List listUserWithEvent() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
return users;
}
}
I am getting the org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at the line with user.getEvents().size();
And last but not least here is the Test class I use:
public class HibernateTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
System.out.println("New user...");
User user = new User();
user.setName("test");
Event event1 = new Event();
event1.setName("Birthday1");
event1.setUser(user);
Event event2 = new Event();
event2.setName("Birthday2");
event2.setUser(user);
udao.saveUser(user);
edao.saveEvent(event1);
edao.saveEvent(event2);
List users = udao.listUserWithEvent();
System.out.println("Events for users");
for (User u : users) {
System.out.println(u.getId() + ":" + u.getName() + " --");
for (Event e : u.getEvents())
{
System.out.println("\t" + e.getId() + ":" + e.getName());
}
}
((ConfigurableApplicationContext)ac).close();
}
}
and here is the Exception:
1621 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
at HibernateTest.main(HibernateTest.java:44)
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
at HibernateTest.main(HibernateTest.java:44)
Things tried but did not work:
assign a threadScope and using beanfactory (I used "request" or "thread" - no difference noticed):
// scope stuff
Scope threadScope = new SimpleThreadScope();
ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
beanFactory.registerScope("request", threadScope);
ac.refresh();
...
Setting up a transaction by getting the session object from the deo:
...
Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction();
tx.begin();
users = udao.listUserWithEvent();
...
getting a transaction within the listUserWithEvent()
public List listUserWithEvent() {
SessionFactory sf = hibernateTemplate.getSessionFactory();
Session s = sf.openSession();
Transaction tx = s.beginTransaction();
tx.begin();
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
tx.commit();
return users;
}
I am really out of ideas by now. Also, using the listUser or listEvent just work fine.
Step forward:
Thanks to Thierry I got one step further (I think). I created the MyTransaction class and do my whole work in there, getting everything from spring. The new main looks like this:
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
// getting dao
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
// gettting transaction template
TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");
MyTransaction mt = new MyTransaction(udao, edao);
transactionTemplate.execute(mt);
((ConfigurableApplicationContext)ac).close();
}
Unfortunately now there is a null-pointer Exception #: user.getEvents().size(); (in the daoImpl).
I know that it should not be null (neither from the output in the console nor from the db layout).
Here is the console output for more information (I did a check for user.getEvent() == null and printed "EVENT is NULL"):
New user...
Hibernate: insert into User (name) values (?)
Hibernate: insert into User (name) values (?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
List users:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
1:User1
2:User2
List events:
Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_
1:Birthday1 for 1:User1
2:Birthday2 for 1:User1
3:Wedding for 2:User2
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
Events for users
1:User1 --
EVENT is NULL
2:User2 --
EVENT is NULL
You can get the sample project from http://www.gargan.org/code/hibernate-test1.tgz (it's an eclipse/maven project)
The solution (for console applications)
There are actually two solutions for this problem - depending on your environment:
For a console application you need a transaction template which captures the actutal db logic and takes care of the transaction:
public class UserGetTransaction implements TransactionCallback{
public List users;
protected ApplicationContext context;
public UserGetTransaction (ApplicationContext context) {
this.context = context;
}
#Override
public Boolean doInTransaction(TransactionStatus arg0) {
UserDao udao = (UserDao) ac.getBean("myUserDAO");
users = udao.listUserWithEvent();
return null;
}
}
You can use this by calling:
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
UserGetTransaction mt = new UserGetTransaction(context);
transactionTemplate.execute(mt);
In order for this to work you need to define the template class for spring (ie. in your basic-db.xml):
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
Another (possible) solution
thanks andi
PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager");
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
boolean success = false;
try {
new UserDataAccessCode().execute();
success = true;
} finally {
if (success) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
}
The solution (for servlets)
Servlets are not that big of a problem. When you have a servlet you can simply start and bind a transaction at the beginning of your function and unbind it again at the end:
public void doGet(...) {
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
// Your code....
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
I think you should not use the hibernate session transactional methods, but let spring do that.
Add this to your spring conf:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
and then I would modify your test method to use the spring transaction template:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// do your hibernate stuff in here : call save, list method, etc
}
}
}
as a side note, #OneToMany associations are lazy by default, so you don't need to annotate it lazy. (#*ToMany are LAZY by default, #*ToOne are EAGER by default)
EDIT: here is now what is happening from hibernate point of view:
open session (with transaction start)
save a user and keep it in the session (see the session cache as an entity hashmap where the key is the entity id)
save an event and keep it in the session
save another event and keep it in the session
... same with all the save operations ...
then load all users (the "from Users" query)
at that point hibernate see that it has already the object in its session, so discard the one it got from the request and return the one from the session.
your user in the session does not have its event collection initialized, so you get null.
...
Here are some points to enhance your code:
in your model, when collection ordering is not needed, use Set, not List for your collections (private Set events, not private List events)
in your model, type your collections, otherwise hibernate won't which entity to fetch (private Set<Event> events)
when you set one side of a bidirectional relation, and you wish to use the mappedBy side of the relation in the same transaction, set both sides. Hibernate will not do it for you before the next tx (when the session is a fresh view from the db state).
So to address the point above, either do the save in one transaction, and the loading in another one :
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// save here
}
}
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// list here
}
}
}
or set both sides:
...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...
(Also do not forget to address the code enhancement points above, Set not List, collection typing)
In case of Web application, it is also possible to declare a special Filter in web.xml, that will do session-per-request:
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
After that you can lazyload your data anytime during the request.
I got here looking for a hint regarding a similar problem. I tried the solution mentioned by Thierry and it didnt work. After that I tried these lines and it worked:
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
Indeed what I'm doing is a batch process that must leverage Spring existings managers/services. After loading the context and doing some invocations I founded the famous issue "failed to lazily initialize a collection". Those 3 lines solved it for me.
The issue is that your dao is using one hibernate session but the lazy load of the user.getName (I assume that is where it throws) is happening outside that session -- either not in a session at all or in another session. Typically we open up a hibernate session before we make DAO calls and don't close it until we are done with all lazy loads. Web requests are usually wrapped in a big session so these problems do not happen.
Typically we have wrapped our dao and lazy calls in a SessionWrapper. Something like the following:
public class SessionWrapper {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public <T> T runLogic(Callable<T> logic) throws Exception {
Session session = null;
// if the session factory is already registered, don't do it again
if (TransactionSynchronizationManager.getResource(sessionFactory) == null) {
session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
try {
return logic.call();
} finally {
// if we didn't create the session don't unregister/release it
if (session != null) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.releaseSession(session, sessionFactory);
}
}
}
}
Obviously the SessionFactory the same SessionFactory that was injected into your dao.
In your case, you should wrap the entire listUserWithEvent body in this logic. Something like:
public List listUserWithEvent() {
return sessionWrapper.runLogic(new Callable<List>() {
public List call() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
}
});
}
You will need to inject the SessionWrapper instance into your daos.
Interesting!
I had the same problem in a #Controller's #RequestMapping handler method.
The simple solution was to add a #Transactional annotation to the handler method so that the session is kept open for the whole duration of the method body execution
Easiest solution to implement:
Within the scope of the session[inside the API annotated with #Transactional], do the following:
if A had a List<B> which is lazily loaded, simply call an API which makes sure the List is loaded
What's that API ?
size(); API of the List class.
So all that's needed is:
Logger.log(a.getBList.size());
This simple call of logging the size makes sure it gets the whole list before calculating the size of the list. Now you will not get the exception !
What worked for us in JBoss was the solution #2 taken from this site at Java Code Geeks.
Web.xml:
<filter>
<filter-name>ConnectionFilter</filter-name>
<filter-class>web.ConnectionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ConnectionFilter</filter-name>
<url-pattern>/faces/*</url-pattern>
</filter-mapping>
ConnectionFilter:
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;
public class ConnectionFilter implements Filter {
#Override
public void destroy() { }
#Resource
private UserTransaction utx;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
utx.begin();
chain.doFilter(request, response);
utx.commit();
} catch (Exception e) { }
}
#Override
public void init(FilterConfig arg0) throws ServletException { }
}
Maybe it would work with Spring too.