I am working on a Spring-MVC application. After the profiler went through the backend, I noticed that getCurrentlyAuthenticatedUser() is an hotspot. For the reason I thought of using cache. Unfortunately it is not working. Once the user logs in, I am getting null user, even though the user is logged in. What is going wrong. Ofcourse when I remove the #Cacheable annotation and config from XML, everything works fine. Any help would be nice.
PersonServiceImpl :
#Service
#Transactional
public class PersonServiceImpl implements PersonService {
private final PersonDAO personDAO;
#Autowired
public PersonServiceImpl(PersonDAO personDAO) {
this.personDAO = personDAO;
}
#Override
#Cacheable(value = "person", condition = "#person == null")
public Person getCurrentlyAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
} else {
return personDAO.findPersonByUsername(authentication.getName());
}
}
}
config for using cache :
<cache:annotation-driven />
<beans:bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<beans:property name="caches">
<beans:set>
<beans:bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="person"/>
</beans:set>
</beans:property>
</beans:bean>
when I call this method, even if logged in or not, I am getting null back. Any help would be nice. Thanks.
In the below code line:-
#Cacheable(value = "person", condition = "#person == null")
Try to use unless in place of condition
Actually you have put up an condition that "Caching will only work if the person is null", which should be like "Caching will work unless person is null".
Happy coding.
Related
I have a web application implemented using Spring and Hibernate. A typical controller method in the application looks like the following:
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody
Foo saveFoo(#RequestBody Foo foo, HttpServletRequest request) throws Exception {
// authorize
User user = getAuthorizationService().authorizeUserFromRequest(request);
// service call
return fooService.saveFoo(foo);
}
And a typical service class looks like the following:
#Service
#Transactional
public class FooService implements IFooService {
#Autowired
private IFooDao fooDao;
#Override
public Foo saveFoo(Foo foo) {
// ...
}
}
Now, I want to create a Log object and insert it to database every time a Foo object is saved. These are my requirements:
The Log object should contain userId from the authorised User object.
The Log object should contain some properties from the HttpServletRequest object.
The save operation and log creation operation should be atomic. I.e. if a foo object is saved in the object we should have a corresponding log in the database indicating the user and other properties of the operation.
Since transaction management is handled in the service layer, creating the log and saving it in the controller violates the atomicity requirement.
I could pass the Log object to the FooService but that seems to be violation of separation of concerns principle since logging is a cross cutting concern.
I could move the transactional annotation to the controller which is not suggested in many of the places I have read.
I have also read about accomplishing the job using spring AOP and interceptors about which I have very little experience. But they were using information already present in the service class and I could not figure out how to pass the information from HttpServletRequest or authorised User to that interceptors.
I appreciate any direction or sample code to fulfill the requirements in this scenario.
There are multiple steps which are to be implemented to solve your problem:
Passing Log object non-obtrusively to service classes.
Create AOP based interceptors to start inserting Log instances to DB.
Maintaining the order to AOP interceptors (Transaction interceptor and Log interceptor) such that transaction interceptor is invoked first. This will ensure that user insert and log insert happens in a single transaction.
1. Passing Log object
You can use ThreadLocal to set the Log instance.
public class LogThreadLocal{
private static ThreadLocal<Log> t = new ThreadLocal();
public static void set(Log log){}
public static Log get(){}
public static void clear(){}
}
Controller:saveFoo(){
try{
Log l = //create log from user and http request.
LogThreadLocal.set(l);
fooService.saveFoo(foo);
} finally {
LogThreadLocal.clear();
}
}
2. Log Interceptor
See how spring AOP works (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html)
a) Create an annotation (acts as pointcut), #Log for method level. This annotation will be put on the service methods for which logging is to be done.
#Log
public Foo saveFoo(Foo foo) {}
b) Create an implementation, LogInteceptor (acts as the advice) of org.aopalliance.intercept.MethodInterceptor.
public class LogInterceptor implements MethodInterceptor, Ordered{
#Transactional
public final Object invoke(MethodInvocation invocation) throws Throwable {
Object r = invocation.proceed();
Log l = LogThreadLocal.get();
logService.save(l);
return r;
}
}
c) Wire the pointcut & advisor.
<bean id="logAdvice" class="com.LogInterceptor" />
<bean id="logAnnotation" class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
<constructor-arg type="java.lang.Class" value="" />
<constructor-arg type="java.lang.Class" value="com.Log" />
</bean>
<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="logAdvice" />
<property name="pointcut" ref="logAnnotation" />
</bean>
3. Ordering of interceptors (transaction and log)
Make sure you implement org.springframework.core.Ordered interface to LogInterceptor and return Integer.MAX_VALUE from getOrder() method. In your spring configuration, make sure your transaction interceptor has lower order value.
So, first your transaction interceptor is called and creates a transaction. Then, your LogInterceptor is called. This interceptor first proceed the invocation (saving foo) and then save log (extracting from thread local).
One more example based Spring AOP but using java configuration, I hate XMLs :) Basically the idea is almost the same as mohit has but without ThreadLocals, Interceptor Orders and XML configs:)
So you will need :
#Loggable annotation to mark methods as the once which create the logs.
TransactionTemplate which we will use to programmatically control the transactions.
Simple Aspect which will put every thing in its place.
So at first lets create the annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Loggable {}
If you are missing the TransactionTemplate configuration or EnableAspectJAutoProxy just add following to your Java Config.
#EnableAspectJAutoProxy
#Configuration
public class ApplicationContext {
.....
#Bean
TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager){
TransactionTemplate template = new TransactionTemplate();
template.setTransactionManager(transactionManager);
return template;
}
}
And next we will need an Aspect which will do all the magic :)
#Component
#Aspect
public class LogAspect {
#Autowired
private HttpServletRequest request;
#Autowired
private TransactionTemplate template;
#Autowired
private LogService logService;
#Around("execution(* *(..)) && #annotation(loggable)")
public void logIt(ProceedingJoinPoint pjp, Loggable loggable) {
template.execute(s->{
try{
Foo foo = (Foo) pjp.proceed();
Log log = new Log();
log.setFoo(foo);
// check may be this is a internal call, not from web
if(request != null){
log.setSomeRequestData(request.getAttribute("name"));
}
logService.saveLog(log);
} catch (Throwable ex) {
// lets rollback everything
throw new RuntimeException();
}
return null;
});
}
}
And finally in your FooService
#Loggable
public Foo saveFoo(Foo foo) {}
Your controller remains the same.
If you use LocalSessionFactoryBean or it's subclass (for instance AnnotationSessionFactoryBean) with inside your Spring context, then the best option would be using entityInterceptor property. You have to pass instance of orh.hibernate.Interceptor interface. For instance:
// java file
public class LogInterceptor extends ScopedBeanInterceptor {
// you may use your authorization service to retrieve current user
#Autowired
private AutorizationService authorizationService
// or get the user from request
#Autowired
private HttpServletRequest request;
#Override
public boolean onSave(final Object entity, final Serializable id, final Object[] state, final String[] propertyNames, final Type[] types) {
// get data from request
// your save logic here
return true;
}
}
// in spring context
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" destroy-method="destroy">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
....
</property>
....
<property name="entityInterceptor" ref="logInterceptor"/>
</bean>
Add the following to your web.xml (or add listener in java code, depending on what you use).
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Add request scope bean, so it'll be request aware.
<bean id="logInterceptor" class="LogInterceptor" scope="request">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
You can separate log data fetch from interceptor, so there will be a different request scoped component, or also you can use filters to store data in ThreadLocal.
I'm trying to achieve authorization for my RestAPI, by letting Spring's Interceptor's (HandlerInterceptorAdapter) PreHandle method check wether the user is in the required role, before the scope hits the requested action in the controller. This requires, however, that I provide each action (URL Path) with the ID of the role it requires. This is my current setup:
public class AuthorizationInterceptor extends HandlerInterceptorAdapter{
#Autowired
IUserService us;
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler, Integer roleId)
throws Exception {
String userId = request.getHeader("UserId");
if(!us.isUserInRole(Long.parseLong(userId), roleId))
return false;
return true;
}
}
And (a part of) the servlet-context.xml:
<interceptors>
<interceptor>
<mapping path="/" />
<mapping path="/users/**" />
<beans:bean class="com.lumi.api.interceptors.AuthorizationInterceptor"></beans:bean>
</interceptor>
</interceptors>
My question is, wether I can pass in the parameter roleId with the bean in the servlet-context config. I can't seem to find anything in the docs. I think I once saw something like:
<mapping path="/" />
<parameter name="something" value="some value">
But i'm not sure.
You can simply set a property using a standard spring sintax, an example
<beans:bean class="com.lumi.api.interceptors.AuthorizationInterceptor">
<beans:property name="roleId" value="REGISTERED_USER"/>
</beans:bean>
your interceptor should of course include the property, so simply
public class AuthorizationInterceptor extends HandlerInterceptorAdapter{
private String roleId;
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
// The rest of your code
}
My application uses Struts2(mvc), Spring (Dependency Injection), JPA with Hibernate, JUnit along with struts2-junit plugin and struts2 spring plugin.
Here is my test class:
#RunWith(SpringJUnit4ClassRunner.class)
public class CustomerSearchIntegrationTest extends StrutsSpringTestCase {
#Test
#Transactional
public void testGetActionProxy() throws Exception {
ActionProxy proxy;
String result;
ActionMapping mapping = getActionMapping("userInfo");
assertNotNull(mapping);
..... // Some JUnit init code..
ActionProxy proxy = getActionProxy("userInfo");
UserInfo user = (UserInfo) proxy.getAction();
result = proxy.execute();
assertEquals("details", result);
System.out.prinltn("Username:" + user.getFirstName());
}
}
GetUserInfo
public class UserInfo extends ActionSupport {
User user; // Entity
UDetails details; // Entity
public String getUserDetails() { //Action method
user = userMgmt.getUser(usrId);
if (user != null ) {
for(UDetails det : user.getUDetails()) { // user.getUDetails() there is where exception gets thrown.
if(det.getStreet().equals(street)){
details = det;
break;
}
}
}
...
...
}
}
User and UDetails are entities. UDetalis is ManyToMany with User and Lazily fetched. All entities are annotated.
UserMgmt
public class UserMgmt {
public User getUser(String userId) {
return userDao.getUser(userId);
}
}
UserDAO
public class UserDAO extends AbstractJPAImpl{
public User getUser(String userId) {
User user = (User) getSession().get(User.class, userId);
return user;
}
}
AbstractJPAImpl
#Transactional
public abstract class AbstractJPAImpl {
private EntityManager em;
#PersistenceContext
protected void setEntityManager(EntityManager em) {
this.em = em;
}
#Transactional
protected Session getSession() {
return (Session) em.getDelegate();
}
....
}
jpaContext.xml
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
// other config stuff
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
All configuration/context files are loading fine. Struts.xml, jpacontext.xml, beans.xml, etc. all are loaded.
But I get an exception:
failed to lazily initialize a collection of role: com.my.data.User.udetails, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
at the following line:
for(UDetails det : user.getUDetails())
Obviously, its trying to load UDetails lazily, then exception is throwing.
However, this application works fine when deployed in a AppServer (WebSphere).
What could I be doing wrong? How do I keep session open?
Update: More info
I am using OpenEntityManagerInViewFilter. My web.xml below
<filter>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<filter-class>
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Update:
This finally works, if I pass the parameter to #PersistenceContext annotation like below:
#PersistenceContext(type=PersistenceContextType.EXTENDED)
So I guess, the transaction is getting closed, but the entities are operable outside of transaction, because of the EXTENDED context type. However, I can't modify the code like above and leave it permanently. So I need to remove it.
So I guess, I have these options, but not sure if these are doable and how:
Get the persistence context from spring application context and pass the parameter. Not sure if this is relevant and possible.
Get the session/entity manager from application context and add another layer of transaction. That ways, the the session runs in two transactions. One is started from my testing code, and another one is in the existing code, which is automatically getting closed, while mine remains open until my test code completes execution.
For the 2nd one, I tried annotatting the method with #Transactional. But that did not work. Not sure why.
Any help?
Update
Looks like, struts-junit plugin does NOT load/read web.xml, which has the OpenEntityManagerInViewFilter. So it did not get loaded.
Any other work around or setup this filter?
If it helps anyone, I couldn't get the #Transaction to work. But I put this:
#PersistenceContext(type=PersistenceContextType.EXTENDED)
and it works now!
.....
see i m havving user.java for the user info.
#Entity(name = "user")
#Table(name = "user")
public class User {
#Id
#Column(name = "id")
private String id;
#Column(name = "password")
private String password;
//getter and setter for this..
}
this is my userdao
public class UserDao {
public interface UserDAO {
public String insert(User user);
}
}
and this is the impl class for inserting into database
see this was my code.please check it and tell me what i m doing wrong
#Repository
#Transactional
public class UserImpl implements UserDAO {
private DataSource dataSource;
#Autowired
SessionFactory sessionFactory;
#Resource(name = "sessionFactory")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Override
public String insert(User use) {
Session session = this.sessionFactory.openSession();
try{
Session sess = this.sessionFactory.getCurrentSession();
session.save(reg);
return (String)user.getUserName();
}catch(Exception e){
System.out.println("in catch"+e.getMessage());
e.printStackTrace();
}
}
}
}
and in my controller after successful registration i m inserting data to the database by this
userDao.insert(user);
but i m not getting output means not any data is inserting into the database.why this..? this is my mvc configuration
<context:component-scan base-package="com.user.xyz" />
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/images/, /resources/css/" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
Remember this: You don't have any exist session, but you use getCurrentSession(). To use this method, you must have a session already. try this:
Session session = this.sessionFactory.openSession();
Now you had a session for your transaction.
You should declare sessionFactory in an xml file (hibernate.xml or spring.xml), then use #Autowire to get it and open new session.
You have 2 choices:
A. force hibernate to flush any changes you have made in session:
session.save(user);
session.flush();
B. make whole method run in transaction - this is much better of course. When transaction is commited - hibernate will flush any changes to database just if you would do manually.
for example:
Transaction tx = null;
try {
session.beginTransaction();
session.save(user);
....
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
If you have multiple DAO calls in a single request, then session.getCurrentSession() is useful, but I guess that is not your case.
If there is only a single DAO call per request then session.openSession() is the right way.
Take care that when using session.openSession(), you also have to close your session afterwards.
The best thing to do is open a session for each incoming request (using a filter is the easiest way to do this) and then calling sessionFactory.getCurrentSession() in all your DAOs to get the current session. I guess your problem is you didn't specify the filter. That way all your DAOs will be easily reusable and you don't need to close your session afterwards.
You can found more information in the docs:
Hibernate documentation
I have also faced the same issue, and got the solution. For now check below points-
Check for appropriate jar files you are using
Check for manifest.mf in META-INF folder for application info.
Check your hibernate configuration in configuration file for hibernate template configuration.
At last if everything is OK then use session.flush() before insert.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
{
{
//... your logic
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close()
Firstly, I can't use the declarative #Transactional approach as the application has multiple JDBC data-sources, I don't want to bore with the details, but suffice it to say the DAO method is passed the correct data-source to perform the logic. All JDBC data sources have the same schema, they're separated as I'm exposing rest services for an ERP system.
Due to this legacy system there are a lot of long lived locked records which I do not have control over, so I want dirty reads.
Using JDBC I would perform the following:
private Customer getCustomer(DataSource ds, String id) {
Customer c = null;
PreparedStatement stmt = null;
Connection con = null;
try {
con = ds.getConnection();
con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
stmt = con.prepareStatement(SELECT_CUSTOMER);
stmt.setString(1, id);
ResultSet res = stmt.executeQuery();
c = buildCustomer(res);
} catch (SQLException ex) {
// log errors
} finally {
// Close resources
}
return c;
}
Okay, lots' of boiler-plate, I know. So I've tried out JdbcTemplate since I'm using spring.
Use JdbcTemplate
private Customer getCustomer(JdbcTemplate t, String id) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
Much nicer, but it's still using default transaction isolation. I need to somehow change this. So I thought about using a TransactionTemplate.
private Customer getCustomer(final TransactionTemplate tt,
final JdbcTemplate t,
final String id) {
return tt.execute(new TransactionCallback<Customer>() {
#Override
public Customer doInTransaction(TransactionStatus ts) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
});
}
But how do I set the transaction isolation here? I can't find it anywhere on the callback or the TransactionTemplate to do this.
I'm reading Spring in Action, Third Edition which explains as far as I've done, though the chapter on transactions continues on to using declarative transactions with annotations, but as mentioned I can't use this as my DAO needs to determine at runtime which data-source to used based on provided arguments, in my case a country code.
Any help would be greatly appreciated.
I've currently solved this by using the DataSourceTransactionManager directly, though it seems like I'm not saving as much boiler-plate as I first hoped. Don't get me wrong, it's cleaner, though I still can't help but feel there must be a simpler way. I don't need a transaction for the read, I just want to set the isolation.
private Customer getCustomer(final DataSourceTransactionManager txMan,
final JdbcTemplate t,
final String id) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
TransactionStatus status = txMan.getTransaction(def);
Customer c = null;
try {
c = t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
} catch (Exception ex) {
txMan.rollback(status);
throw ex;
}
txMan.commit(status);
return c;
}
I'm still going to keep this one unanswered for a while as I truly believe there must be a better way.
Refer to Spring 3.1.x Documentation - Chapter 11 - Transaction Management
Using the TransactionTemplate helps you here, you need to configure it appropriately. The transaction template also contains the transaction configuration. Actually the TransactionTemplate extends DefaultTransactionDefinition.
So somewhere in your configuration you should have something like this.
<bean id="txTemplate" class=" org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="readOnly" value="true" />
<property name="transactionManager" ref="transactionManager" />
</bean>
If you then inject this bean into your class you should be able to use the TransactionTemplate based code you posted/tried earlier.
However there might be a nicer solution which can clean up your code. For one of the projects I worked on, we had a similar setup as yours (single app multiple databases). For this we wrote some spring code which basically switches the datasource when needed. More information can be found here.
If that is to far fetched or overkill for your application you can also try and use Spring's AbstractRoutingDataSource, which based on a lookup key (country code in your case) selects the proper datasource to use.
By using either of those 2 solutions you can start using springs declarative transactionmanagement approach (which should clean up your code considerably).
Define a proxy data source, class being org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy and set the transaction isolation level. Inject actual data source either through setter or constructor.
<bean id="yourDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<constructor-arg index="0" ref="targetDataSource"/>
<property name="defaultTransactionIsolationName" value="TRANSACTION_READ_UNCOMMITTED"/>
</bean>
I'm not sure you can do it without working with at the 'Transactional' abstraction-level provided by Spring.
A more 'xml-free' to build your transactionTemplate could be something like this.
private TransactionTemplate getTransactionTemplate(String executionTenantCode, boolean readOnlyTransaction) {
TransactionTemplate tt = new TransactionTemplate(transactionManager);
tt.setReadOnly(readOnlyTransaction);
tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return tt;
}
In any case I would "leverage" the #Transactional annotation specifying the appropriate transaction manager, binded with a separate data-source. I've done this for a multi-tenant application.
The usage:
#Transactional(transactionManager = CATALOG_TRANSACTION_MANAGER,
isolation = Isolation.READ_UNCOMMITTED,
readOnly = true)
public void myMethod() {
//....
}
The bean(s) declaration:
public class CatalogDataSourceConfiguration {
#Bean(name = "catalogDataSource")
#ConfigurationProperties("catalog.datasource")
public DataSource catalogDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = ENTITY_MANAGER_FACTORY)
public EntityManagerFactory entityManagerFactory(
#Qualifier("catalogEntityManagerFactoryBean") LocalContainerEntityManagerFactoryBean emFactoryBean) {
return emFactoryBean.getObject();
}
#Bean(name= CATALOG_TRANSACTION_MANAGER)
public PlatformTransactionManager catalogTM(#Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public NamedParameterJdbcTemplate catalogJdbcTemplate() {
return new NamedParameterJdbcTemplate(catalogDataSource());
}
}