Spring Hibernate JPA not saving multiple rows in a same table - spring

I am working on a requirement in which user can send messages to multiple people, I have to save those messages in a message table.
I am using JPA entityManager for persisting objects and Hibernate as a persistence provider and Spring declarative transaction management.
Even I am executing the persist() method for three times it is saving only one row in the table. I don't know what should I do to save all the messages. It is not displaying any exception message.
Following is my declarative transaction management configuration in applicationContext.xml
<tx:advice id="txAdvice" >
<tx:attributes>
<tx:method
name="*"
propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut
id="messageServiceOperation"
expression="execution(* com.abhinow.message.services.*.*(..))" />
<aop:advisor
advice-ref="txAdvice"
pointcut-ref="messageServiceOperation" />
</aop:config>
Following is code in my service class MessageService.java
private void saveMultipleMessages(SentMessage message) {
String[] toAddresses = message.getMultipleDestinations().split(",");
for(String to: toAddresses) {
message.setTo(to);
saveMessage(message);
}
}
public void saveMessage(SentMessage message) {
messageRepository.saveSentMessage(message);
}
Following is code in my MessageRepository.java
#Repository
public class MessageRepository {
#PersistenceContext
EntityManager entityManagerFactory;
public void saveSentMessage(SentMessage message) {
entityManagerFactory.persist(message);
}
}
Any help would be appreciated. Thanks in advance

It looks like you are re-using the same SentMessage object in your for loop. My guess is that JPA will detect that it already persisted this object and will not persist it again.
Try something like this:
private void saveMultipleMessages(SentMessage message) {
String[] toAddresses = message.getMultipleDestinations().split(",");
for(String to: toAddresses) {
SentMessage toSave = message.withToAddress(to);
saveMessage(toSave);
}
}
Where "withToAddress" creates a new SentMessage object based on the current one, but with a different to address. When you use this technique, you can make SentMessage immutable, which often has advantages over mutable objects.

Related

Get the object which failed validation Spring Batch validation

I am having this task to process input .csv, .txt files and store the data into a database. I am using Spring Batch for this purpose. Before dumping the data into database, I have to perform some validation checks on the data. I am using Spring Batch's ValidatingItemProcessor and Hibernate's JSR-303 reference implementation hibernate validator for the same. The code looks something like:
public class Person{
#Pattern(regexp = "someregex")
String name;
#NotNull
String address;
#NotNull
String age;
//getters and setters
}
And then I wrote a validator which looks something like this --
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.batch.item.validator.Validator;
class MyBeanValidator implements Validator<Person>, InitializingBean{
private javax.validation.Validator validator;
#Override
public void afterPropertiesSet() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
#Override
public void validate(Person person) throws ValidationException {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(person);
if(constraintViolations.size() > 0) {
generateValidationException(constraintViolations);
}
}
private void generateValidationException(Set<ConstraintViolation<Object>> constraintViolations) {
StringBuilder message = new StringBuilder();
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
message.append(constraintViolation.getMessage() + "\n");
}
throw new ValidationException(message.toString());
}
And then I have a processor which subclasses Spring Batch's ValidatingItemProcessor.
public class ValidatingPersonItemProcessor extends ValidatingItemProcessor<Person>{
#Override
public Person process(Person person) {
//some code
}
The records that pass validation checks would be passed on to another processor for further processing but the failed ones will be cleaned and then passed on to next processor.
Now I want to catch hold of records which failed validation. My objective is to report all input records that failed validation and clean those records further before I could pass on those records to next processor for further processing. How can I achieve this?
Will the Spring Batch process terminate if validation fails for some input? If yes, how to avoid that? My Processor configuration looks something like :
<batch:chunk reader="personItemReader" writer="personDBWriter" processor="personProcessor"
commit-interval="100" skip-limit="100">
<batch:skippable-exception-classes>
<batch:include class="org.springframework.batch.item.validator.ValidationException"/>
</batch:skippable-exception-classes>
<batch:listeners>
<batch:listener>
<bean
class="org.someorg.poc.batch.listener.PersonSkipListener" />
</batch:listener>
</batch:listeners>
</batch:chunk>
<bean id="personProcessor"
class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<ref bean="validatingPersonItemProcessor" />
<ref bean="personVerifierProcessor" />
</list>
</property>
</bean>
<bean id="validatingPersonItemProcessor" class="org.someorg.poc.batch.processor.ValidatingPersonItemProcessor" scope="step">
<property name="validator" ref="myBeanValidator" />
</bean>
<bean id="myBeanValidator" class="org.someorg.poc.batch.validator.MyBeanValidator">
</bean>
<bean id="personVerifierProcessor" class="org.someorg.poc.batch.processor.PersonVerifierProcessor" scope="step"/>
</beans>
I guess your validatingPersonItemProcessor bean has his validator parameter set with your myBeanValidator. So the Exception will be thrown by the processor.
Create your own SkipListener. Here you put the logic on what happens when an item is not validated (writtes to a file, a DB, etc.), in the onSkipInProcess();.
You need to add the ValidationException you throw in <batch:skippable-exception-classes> so they will be caught (and doesn't terminate your batch), and add your SkipListener in the <batch:listeners>, so it will be call when an exception is thrown.
EDIT: Answer to comment.
If your processor is a ValidatingItemProcessor and you set the validator, it should automatically call validate. However if you make your own ValidatingItemProcessor by extending it, you should explicitely call super.process(yourItem); (process() of ValidatingItemProcessor ) to validate your item.

HibernateTemplate save performs inserts but not updates

I have a typical Spring / Hibernate setup. Here's my spring config:
<context:annotation-config />
<context:component-scan base-package="com.myco.myapp.modules" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
...
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
I have a BaseRepository:
#Transactional(propagation = Propagation.MANDATORY)
public final T save(final T entity) throws RepositoryException {
try {
getHibernateTemplate().save(entity);
return entity;
} catch (DataAccessException e) {
throw new EntityCouldNotBeSavedException(getPersistentClass(),
e);
}
}
And a Repository class that extends it:
#Repository
public class PersonRepositoryImpl extends BaseRepositoryImpl<Person, String>
And a Service:
#Service
public class PersonServiceImpl {
#Autowired
private PersonRepository _personRespository;
I call the following method, saveSomeStuff(), an when I insert using BaseRepository.save() it works perfectly. But when I try to update, it doesn't make the change:
#Override
#Transactional
public void saveSomeStuff() {
try {
Person existingPerson = _personRespository.findById("1");
existingPerson.setName("John");
_personRespository.save(existingPerson);
Person dbExistingPerson = _personRespository.findById("1");
// This prints "John".
System.out.println(dbExistingPerson.getName());
Person newPerson = new Person();
newPerson.setName("Jack");
_personRespository.save(newPerson);
} catch (RepositoryException e) {
e1.printStackTrace();
}
}
I thought I might have a transaccionality problem, but as I said, upon leaving the Service method the new Person is persisted in the database. In the log I see:
insert into person ...
However, the update I made is not persisted, and there is no error and no 'update' sql statement in the log. I thought the HibernateTemplate.save() method might be the problem but from within the saveSomeStuff() method, after loading the Person from the database, I do a System.out, and the Person loaded from the database has the updated name.
What am I missing here?
There is a separate method, saveOrUpdate(entity). You can use it if you don't want hibernate to generate id while saving.
Save method will Persists an entity. Will assign an identifier if one doesn't exist. If one does, it's essentially doing an update. Returns the generated ID of the entity.
Figured out the problem. If I had included my Entity class, someone probably would have seen it sooner than me.
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
#Immutable
#Table(name = "PEOPLE")
public class Person {
...
}
Initially I was getting a cache error:
java.lang.UnsupportedOperationException: Can't write to a readonly object
The quick solution? Add the #Immutable annotation. But if you read the docs for it:
An immutable entity may not be updated by the application.
Updates to an immutable entity will be ignored, but no exception is thrown.
Which explains why 1) updates were being ignored and 2) no exceptions were being thrown.
So I got rid of the #Immutable annotation and changed Cache to:
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
And now everything works fine.
In summary: rtfm.
I had stumbled upon the same problem. The entity was getting inserted into the database, but while updating some of the columns where not getting updated and there were no errors in the log. After going through the entity class, I figured out that I had annotated some of my fields as below
#Column(name = "CREATED_DT", updatable = false)
private Date createdOn;
After removing the updatable attribute from the annotation, the update was working fine.

Spring 3.1 and Oracle Audit Trail: Providing application data to triggers

Problem Parameters:
Spring 3.1
Oracle 11.2.0.3
Glassfish 2.1 Application Server, providing a JDBC connection pool.
Problem Description:
I am retrofitting user auditing in an existing set of administrative applications for adding, editing and deleting customer users. I need to store the ID of the administrative user in audit records created by Oracle triggers associated with a number of tables. I want to make the administrative user Id accessible to the triggers by setting the Oracle CLIENT_IDENTIFIER attribute on each a connection retrieved from the connection pool before a database operation and then clear the attribute after the database operation. I have something that works, but I don't really like the way it is done.
The Question:
Is there a way to access connections so an Oracle context attribute can be set before and after a database operation? Maybe some kind of listener responding to an event?
I have looked at:
A million web pages (OK maybe that's an exaggeration, but I've googled for a three or four hours).
Using DataSourceUtils to get connections. This would work, but I really don't want to manage the connections, I just want to intercept them on the way in and out of the pool to set the CLIENT_IDENTIFIER attribute value.
Overriding the getConnection method of the datasource. Since this gets called somewhere inside the JdbcTemplate, I can't get the application data to the method.
I'm hoping that the Spring and/or Oracle Gurus will say "Well it's obvious and the answer is ... " without having to hack through my implementation, but here it is anyway. If nothing else, this does work in case someone is looking for an idea.
My Implementation:
All database operations are done using a JdbcOperations reference to a JdbcTemplate object injected into a Dao. The add, edit and delete operation use the JdbcOperations query method, passing either a PreparedStatementCreator or a BatchPreparedStatementSetter. I access the java.sql.Connection object provided by the application server connection pool in the callback methods for these objects (createPreparedStatement or setValues) to set the CLIENT_IDENTIFIER attribute.
applicationContext.xml datasource configuration:
<!-- Setup the datasource -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/IpOneDatabasePool"/>
</bean>
<!-- Setup the transaction manager -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- Associate the transaction manager with objects that must be managed. -->
<aop:config>
<aop:pointcut id="userDaoOperation" expression="execution(* com.myCompany.IpOne.dao.UserDaoImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="userDaoOperation"/>
</aop:config>
<!-- Bean providing access to the various prepared statement objects -->
<bean id="daoHelperFactory" class="com.myCompany.IpOne.dao.DaoHelperFactoryImpl" />
<!-- Bean that allows setting of the client identifier for the audit trail -->
<bean id="databaseContextEditor" class="com.myCompany.IpOne.dao.OracleDatabaseContextEditor" />
<!-- Dao that manages persistence of User objects -->
<bean id="userDao" class="com.myCompany.IpOne.dao.UserDaoImpl" >
<property name="dataSource" ref="dataSource"/>
<property name="licenseDao" ref="licenseDao"/>
<property name="appPropertyManager" ref="appPropertyManager"/>
<property name="maximumLicensesPerUserKey" value="max_licences_per_user"/>
<property name="daoHelperFactory" ref="daoHelperFactory"/>
</bean>
This is the user Dao interface
public interface UserDao {
void addUser(User newUser,String adminUserId);
}
This is the user Dao class
public class UserDaoImpl implements UserDao{
private JdbcOperations jdbcOperations;
public void setDataSource(DataSource dataSource) {
this.jdbcOperations = new JdbcTemplate(dataSource);
}
public void addUser(User newUser,String adminUserId) {
PreparedStatementCreator insertUserStatement =
this.daoHelperFactory.getInsertUserStatement(newUser,adminUserId);
KeyHolder keyHolder = this.daoHelperFactory.getKeyHolder();
this.jdbcOperations.update(insertUserStatement, keyHolder);
newUser.setUserId(keyHolder.getKey().intValue());
}
}
This class provides access to the application context.
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return ctx;
}
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
}
Interface for classes that provides various objects used by the Dao.
public interface DaoHelperFactory {
PreparedStatementCreator getInsertUserStatement(User user,String adminUserId);
KeyHolder getKeyHolder();
}
This class is just a factory for PreparedStatementCreator and BatchPreparedStatementSetter objects and other objects used by the Dao. I've changed it to provide the object that actually sets the database context attribute to the various objects being returned.
public class DaoHelperFactoryImpl implements DaoHelperFactory{
private DatabaseContextEditor getDatabaseContextEditor(){
ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
DatabaseContextEditor databaseContextEditor = (DatabaseContextEditor) appContext.getBean("databaseContextEditor");
return databaseContextEditor;
}
public KeyHolder getKeyHolder(){
return new GeneratedKeyHolder();
}
public PreparedStatementCreator getInsertUserStatement(User user,String adminUserId){
InsertUser insertUser = new InsertUser(user,adminUserId);
insertUser.setDatabaseContextEditor(getDatabaseContextEditor());
return insertUser;
}
}
This is the interface for classes that set the database context
public interface DatabaseContextEditor {
public DatabaseContextEditor getInstance();
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException;
}
This is class which does that for Oracle
public class OracleDatabaseContextEditor implements DatabaseContextEditor{
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException{
OracleJdbc4NativeJdbcExtractor extractor = new OracleJdbc4NativeJdbcExtractor();
oracle.jdbc.OracleConnection oracleConnection = null;
if(!(connection instanceof oracle.jdbc.OracleConnection))
oracleConnection = (oracle.jdbc.OracleConnection) extractor.getNativeConnection(connection);
else
oracleConnection = (oracle.jdbc.OracleConnection)connection;
String[] metrics = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX]=clientIdentifier;
oracleConnection.setEndToEndMetrics(metrics,(short)0);
}
public DatabaseContextEditor getInstance(){
return new OracleDatabaseContextEditor();
}
}
This class is the PreparedStatementCreator for adding a User
public class InsertUser implements PreparedStatementCreator {
User insertUser;
/** This is the admin user Id I need to store */
String adminUserId;
private final String SQL = "INSERT INTO SC_USR (" +
"USR_ID, USR_SSO_NAME, USR_PH_NO, USR_SIP_NAME," +
"USR_SIP_PSWD, USR_SIP_DISP_NAME, USR_SIP_DOMAIN, USR_SIP_PROXY," +
" USR_CREATED_BY, USR_CREATED_DATETIME) " +
"VALUES (SEQ_SC_USR_ID.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, SYSTIMESTAMP)";
private final String GENERATED_COLUMNS[] = {"USR_ID"};
/** Object that provides functionality for setting values in the database context */
private DatabaseContextEditor databaseContextEditor;
public InsertUser(User user,String adminUserId){
this.insertUser = user;
this.adminUserId = adminUserId;
}
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
this.databaseContextEditor.setClientIdentifier(connection, adminUserId);
PreparedStatement preparedStatement = connection.prepareStatement(SQL,GENERATED_COLUMNS);
int i=1;
preparedStatement.setString(i++,this.insertUser.getSsoName());
preparedStatement.setString(i++,this.insertUser.getPhoneNumber());
preparedStatement.setString(i++,this.insertUser.getSipName());
preparedStatement.setString(i++,this.insertUser.getSipPassword());
preparedStatement.setString(i++,this.insertUser.getSipDisplayName());
preparedStatement.setString(i++,this.insertUser.getSipDomain());
preparedStatement.setString(i++,this.insertUser.getSipProxy());
preparedStatement.setString(i++,this.insertUser.getCreatedBy().name());
return preparedStatement;
}
public void setDatabaseContextEditor(DatabaseContextEditor databaseContextEditor) {
this.databaseContextEditor = databaseContextEditor;
}
}
There are "AFTER DELETE OR INSERT OR UPDATE" triggers on each table I want to audit. Each table has a corresponding audit table. They extract the CLIENT_IDENTIFIER from the context and insert a row in the appropriate audit table. This is a sample.
CREATE OR REPLACE TRIGGER IPONE_DEV_USER.SC_USR$AUDTRG
AFTER DELETE OR INSERT OR UPDATE
ON IPONE_DEV_USER.SC_USR
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10) := NULL;
v_admin_user_id VARCHAR2(30);
BEGIN
v_admin_user_id := SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER');
IF INSERTING THEN
v_operation := 'INS';
ELSIF UPDATING THEN
v_operation := 'UPD';
ELSE
v_operation := 'DEL';
END IF;
IF INSERTING OR UPDATING THEN
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SOME_VALUE1,
USR_SOME_VALUE2,
USR_SOME_VALUE3,
USR_SOME_VALUE4,
USR_CREATED_BY,
USR_SOME_VALUE5,
USR_SOME_VALUE6,
aud_action,aud_timestamp,aud_user) VALUES (
:new.USR_ID,
:new.USR_SSO_NAME,
:new.USR_PH_NO,
:new.USR_SOME_VALUE1,
:new.USR_SOME_VALUE2,
:new.USR_SOME_VALUE3,
:new.USR_CREATED_DATETIME,
:new.USR_CREATED_BY,
:new.USR_SOME_VALUE4,
:new.USR_SOME_VALUE5,
v_operation,SYSDATE,v_admin_user_id);
ELSE
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SIP_NAME,
USR_SIP_PSWD,
USR_SIP_DISP_NAME,
USR_CREATED_DATETIME,
USR_CREATED_BY,
USR_SIP_DOMAIN,
USR_SIP_PROXY,
aud_action,aud_timestamp,aud_user) VALUES (
:old.USR_ID,
:old.USR_SSO_NAME,
:old.USR_PH_NO,
:old.USR_SIP_NAME,
:old.USR_SIP_PSWD,
:old.USR_SIP_DISP_NAME,
:old.USR_CREATED_DATETIME,
:old.USR_CREATED_BY,
:old.USR_SIP_DOMAIN,
:old.USR_SIP_PROXY,
v_operation,SYSDATE,v_admin_user_id);
END IF;
END;
As I say this works, but I don't like it for the following reasons.
I have to modify the connection in methods that are intended for setting up the prepared statements.
I have to add this code to every PreparedStatementCreator or a BatchPreparedStatementSetter object I want to audit.
I don't have access to the connection after the database operation so I can clear the attribute.
What I really want is a single point where I can set the attribute on the connection, before and after.
Any input or ideas would be appreciated.
Spring have an elegant way of doing this. The example is pretty much what you want:
Spring Data docs
Uses AOP to set the CLIENT_IDENTIFIER when a connection is got from the Datasource.
Could use another pointcut for when the connection is closed. But not a problem is the connection pool is used soley by your app.
A better way is to use connection labeling. Have a look at the oracle.ucp.jdbc.LabelableConnection interface.

Spring: Inject bean depended on context (session/web or local thread/background process)

Is it possible to create a factory or proxy that can decide if thread is running in (Web)Request or background-process (ie. scheduler) and then depending on that information, it creates a session bean or a prototype bean?
Example (pseudo Spring config :)
<bean id="userInfoSession" scope="session" />
<bean id="userInfoStatic" scope="prototype" />
<bean id="currentUserInfoFactory" />
<bean id="someService" class="...">
<property name="userInfo" ref="currentUserInfoFactory.getCurrentUserInfo()" />
</bean>
I hope this makes my question easier to understand...
My Solution
It's never to late to update own questions ;). I solved it with two different instances of client session, one SessionScoped client session and one SingletonScoped session. Both are normal beans.
<bean id="sessionScopedClientSession" class="com.company.product.session.SessionScopedClientSession" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="singletonScopedClientSession" class="com.company.product.session.SingletonScopedClientSession" />
<bean id="clientSession" class="com.company.product.session.ClientSession">
<property name="sessionScopedClientSessionBeanName" value="sessionScopedClientSession" />
<property name="singletonScopedClientSessionBeanName" value="singletonScopedClientSession" />
</bean>
The ClientSession will then decide if singleton or session scope:
private IClientSession getSessionAwareClientData() {
String beanName = (isInSessionContext() ? sessionScopedClientSessionBeanName : singletonScopedClientSessionBeanName);
return (IClientSession) ApplicationContextProvider.getApplicationContext().getBean(beanName);
}
Where session type could be gathered through this:
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
All the classes implement a interface called IClientSession. Both singletonScoped and sessionScoped beans extends from a BaseClientSession where the implementation is found.
Every service then can use the client session ie:
#Resource
private ClientSession clientSession;
...
public void doSomething() {
Long orgId = clientSession.getSomethingFromSession();
}
Now if we go one step further we can write something like a Emulator for the session. This could be done by initializing the clientSession (which is in no context of a request) the singleton session. Now all services can use the same clientSession and we still can "emulate" a user ie:
clientSessionEmulator.startEmulateUser( testUser );
try {
service.doSomething();
} finally {
clientSessionEmulator.stopEmulation();
}
One more advice: take care about threading in SingletonScoped clientSession instance! Wouw, I thought I could do it with less lines ;) If you like to know more about this approach feel free to contact me.
I created small universal workaround to inject beans depends on context.
Guess we have two beans:
<bean class="xyz.UserInfo" id="userInfo" scope="session" />
<bean class="xyz.UserInfo" id="userInfoSessionLess" />
We want to use "userInfo" bean for web user actions and "userInfoSessionLess" bean for background services for example.
Wa also want to write code and don't want to think about context, for example:
#Autowired
//You will get "java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request?" for session less services.
//We can fix it and autowire "userInfo" or "userInfoSessionLess" depends on context...
private UserInfo userInfo;
public save(Document superSecureDocument) {
...
superSecureDocument.lastModifier = userInfo.getUser();
...
}
Now we need create custom session scope to make it worked:
public class MYSessionScope extends SessionScope implements ApplicationContextAware {
private static final String SESSION_LESS_POSTFIX = "SessionLess";
private ApplicationContext applicationContext;
public Object get(String name, ObjectFactory objectFactory) {
if (isInSessionContext()) {
log.debug("Return session Bean... name = " + name);
return super.get(name, objectFactory);
} else {
log.debug("Trying to access session Bean outside of Request Context... name = " + name + " return bean with name = " + name + SESSION_LESS_POSTFIX);
return applicationContext.getBean(name.replace("scopedTarget.", "") + SESSION_LESS_POSTFIX);
}
}
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Register new scope:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="mySession">
<bean class="com.galantis.gbf.web.MYSessionScope" />
</entry>
</map>
</property>
</bean>
Now we need modify beans definions like this:
<bean class="xyz.UserInfo" id="userInfo" scope="mySession" autowire-candidate="true"/>
<bean class="xyz.UserInfo" id="userInfoSessionLess" autowire-candidate="false"/>
That's all. Bean with name "SessionLess" will be used for all "mySession" scoped beans if we use bean outside of actual web request thread.
Your rephrase is indeed considerably simpler :)
Your currentUserInfoFactory could make use of RequestContextHolder.getRequestAttributes(). If a session is present and associated with the calling thread, then this will return a non-null object, and you can then safely retrieve the session-scoped bean from the context. If it returns a null, then you should fetch the prototype-scoped bean instead.
It's not very neat, but it's simple, and should work.
Create two custom context loaders that bind the same scope defintion to different implementations:
public final class SessionScopeContextLoader extends GenericXmlContextLoader {
protected void customizeContext(final GenericApplicationContext context) {
final SessionScope testSessionScope = new SessionScope();
context.getBeanFactory().registerScope("superscope", testSessionScope);
}
...
}
Then you make a corresponding one for singleton (make your own scope with just statics)
Then you just specify the appropriate context loader in the xml startup for each of the two contexts.

Spring #Transactional wrapping 2 methods

I'm a Spring newby. I use the #Transactional annotation for my dao methods:
#Transactional
public Person getById(long id) {
return new Person(jdbcTemplate.queryForMap(...));
}
#Transactional
public void save(Person person) {
jdbcTemplate.update(...);
}
and I've set up the transaction manager like this:
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
The problem is that when my client code calls dao.save(..) and then dao.getById(4) these happen in two separate transactions. How is it possible to wrap those 2 calls in the same database transaction? Ideally without doing it in a programmatic way.
thanks
It is bad practice to put transactional attributes in DAO layer. Also, I am not sure why do you require transaction for getById method. Even if you want to use transaction then you need to specify propagation behaviour as REQUIRES_NEW for save and getById method.
#Transactional(propagation = REQUIRES_NEW, readOnly = false)
public Person saveAndGetById(Person person, long id) {
save(person);
return getById(id);
}
#Transactional(propagation = REQUIRED)
public Person getById(long id) {
return new Person(jdbcTemplate.queryForMap(...));
}
#Transactional(propagation = REQUIRED, readOnly = false)
public void save(Person person) {
jdbcTemplate.update(...);
}
However, the best thing would be to have the "save" method return an ID, because it is hard to know beforehand which ID the Person will have once persisted.
Good practice in this case would be marking service method which invokes both these DAO methods as #Transactional. The case was clearly discussed here.

Resources