Use Spring annotations to automatically apply Hibernate Interceptor? - java

In my service class I need the hibernate session available. I currently do this in the beans.xml:
<bean id = "userDao" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="userDaoTarget" />
</property>
<property name="proxyInterfaces">
<value>com.app.dao.UserDao</value>
</property>
<property name="interceptorNames">
<list>
<value>hibernateInterceptor</value>
</list>
</property>
<qualifier value="proxy" />
</bean>
...
<bean id="hibernateInterceptor"
class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
<bean>
(copied by hand, may be some typos..)
I'm moving to using annotations over XML, I was wondering if there was a way to use them to configure the proxy as I have above including the hibernate interceptor? If not - is there a way that I can reduce the amount of XML (with about 7 DAOs it makes it very cluttered)

Ok, Let's go. You said
I am moving to using annotations over XML
Enable an aspect as follows
package br.com.ar.aop;
#Aspect
public class HibernateInterceptorAdvice {
#Autowired
private HibernateInterceptor hibernateInterceptor;
/**
* I suppose your DAO's live in com.app.dao package
*/
#Around("execution(* com.app.dao.*(..))")
public Object interceptCall(ProceedingJoinPoint joinPoint) throws Throwable {
ProxyFactory proxyFactory = new ProxyFactory(joinPoint.getTarget());
proxyFactory.addAdvice(hibernateInterceptor);
Class [] classArray = new Class[joinPoint.getArgs().length];
for (int i = 0; i < classArray.length; i++)
classArray[i] = joinPoint.getArgs()[i].getClass();
return
proxyFactory
.getProxy()
.getClass()
.getDeclaredMethod(joinPoint.getSignature().getName(), classArray)
.invoke(proxyFactory.getProxy(), joinPoint.getArgs());
}
}
But keep in mind It just works if your DAO's implements some interface (For instance, UserDAOImpl implements UserDAO). Spring AOP uses JDK dynamic proxy in this case. If you does not have any interface, you can rely on your IDE To refactor your code by using Extract interface
Declare your xml as follows (Be aware i am using Spring 2.5 xsd schema)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--SessionFactory settings goes here-->
<bean class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory" ref="sessionFactory"/>
<bean>
<!--To enable AspectJ AOP-->
<aop:aspectj-autoproxy/>
<!--Your advice-->
<bean class="br.com.ar.aop.HibernateInterceptorAdvice"/>
<!--Looks for any annotated Spring bean in com.app.dao package-->
<context:component-scan base-package="com.app.dao"/>
<!--Enables #Autowired annotation-->
<context:annotation-config/>
</beans>
Do not forget To put in the classpath besides Spring libraries
<SPRING_HOME>/lib/asm
<SPRING_HOME>/lib/aopalliance
<SPRING_HOME>/lib/aspectj

Have a look at the #Autowired annotation.

Related

Building standalone Spring Boot app with hibernate connection

I'm trying to build a simple application, which connects with database and saves some data in it, let's say once per hour. I found some tutorials on pages like baeldung, but their solutions doesn't work for me.
Here is my configuration file:
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<context:component-scan base-package="io.github.steve"/>
<!-- Step 4: Add support for conversion, formatting and validation support -->
<mvc:annotation-driven/>
<!-- Step 5: Define Spring MVC view resolver -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames" value="messages"/>
</bean>
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/articles?useSSL=false"/>
<property name="user" value="root"/>
<property name="password" value=""/>
<property name="minPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<property name="maxIdleTime" value="30000"/>
</bean>
<bean id="sessionFactoryXXX" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="packagesToScan" value="io.github.steve.webscraping.domain"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="myTransactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactoryXXX"/>
</bean>
<tx:annotation-driven transaction-manager="myTransactionManager"/>
</beans>
ArticlesDaoImpl:
#Repository
public class ArticlesDaoImpl implements ArticlesDao {
#Autowired
SessionFactory sessionFactory;
#Override
public List<Article> getAllArticles() {
Session session = sessionFactory.getCurrentSession();
Query<Article> query = session.createQuery("from Article order by uploadDate", Article.class);
return query.getResultList();
}
#Override
public void addArticle(Article article) {
Session session = sessionFactory.getCurrentSession();
session.persist(article);
}
}
ArticlesServiceImpl:
#Service
public class ArticlesServiceImpl implements ArticlesService {
#Autowired
private ArticlesDao articlesDao;
#Override
#Transactional
public List<Article> getAllArticles() {
return articlesDao.getAllArticles();
}
#Override
#Transactional
public void addArticle(Article article) {
articlesDao.addArticle(article);
}
#Override
#Transactional
public void addArticles(List<Article> articles) {
articles.stream().forEach(articlesDao::addArticle);
}
}
And now:
First of all, I don't know where should I put my xml config file.
I have no idea how to build my Main.class. I want to Autowire ArticlesService to Main class and run a method from it. When i use just SpringApplication.run(Main.class, args), it loads and finishing with exit code 1.
Main app:
#SpringBootApplication
#EnableScheduling
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
ScheduledTasks:
#Component
public class ScheduledTasks {
#Autowired
ArticlesService articlesService;
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
#Scheduled(cron = "* * * * *")
public void reportCurrentTime() {
articlesService.addArticles(new MkyongWebScraper().getArticlesList());
}
}
I'm really new in Spring and I know this question is pretty wide, but I can't find any suitable sources for my tasks. What am I doing wrong?
I am afraid the code you are writing, especially the XML config does not take advantage of the easier and more convenient offered with Spring Boot, and Spring Boot 2.
To generate the skeleton of your project, go to Spring Boot Initializer. You can choose the dependencies you want (in your case, it should "JPA" which is based on Spring Data JPA) and then you can download a zip with your project.
You might want to have a look at Spring Boot documentation. There are also some pretty interesting videos to "get started with Spring Boot" on youtube..
Once you have your project, you can add Spring Data JPA code...
Spring Boot documentation has a section about Spring Data JPA, but you can also find more info in the documentation of Spring Data JPA project.
Best of luck
I already solved my problem. I just didn't understood Spring enough. For people like me I recommend Spring in Action by Craig Walls.

Qualifier doesn't work for DataSource in Spring Boot

I have two datasources defined "datasource1" and "datasource2" (in xml configuration from a dependency).
Because of that I don't get JdbcTemplate configured by default so I need to do it manually, I do it like this:
1.
#Bean
public JdbcOperations jdbcOperations(DataSource datasource1) {
return new JdbcTemplate(datasource1);
}
2.
#Bean
public JdbcOperations jdbcOperations(#Qualifier("datasource1") DataSource datasource1) {
return new JdbcTemplate(datasource1);
}
In both cases it fails with:
Parameter 0 of method jdbcOperations in com.example.PersistentConfig required a single bean, but 2 were found:
- datasource1: defined in class path resource [datasources.xml]
- datasource2: defined in class path resource [datasources.xml]
Why the qualifier doesn't work?
I can't change the datasources.xml file to add a primary=true to datasource.
datasources.xml:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="datasource1"
class="com.example.database.IdentifiedLazyConnectionDataSourceProxy">
<qualifier value="datasource1"/>
<property name="targetDataSource">
<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/ak1Database"/>
<property name="resourceRef" value="true"/>
</bean>
</property>
<property name="identifier" value="shared"/>
</bean>
<bean id="datasource2"
class="com.example.database.IdentifiedLazyConnectionDataSourceProxy">
<qualifier value="datasource2"/>
<property name="targetDataSource">
<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/ak2Database"/>
<property name="resourceRef" value="true"/>
</bean>
</property>
<property name="identifier" value="shared"/>
</bean>
</beans>
The reason this wasn't working is because xml configuration always overrides java config (see https://jira.spring.io/browse/SPR-7028).
To solve this I needed to create a bean that has different name then the one in the xml and mark that new bean as #Primary.
So now I will have three datasource beans, two connecting to the same database schema, but only one of the marked Primary so it will be used in default places instead of the xml defined one.

#Transactional don't work, maybe bad configuration for sessionfactory.getCurrentSession() [duplicate]

I'm getting the above exception with Spring3 and Hibernte4
The following is my bean xml file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<context:annotation-config/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/GHS"/>
<property name="username" value="root"/>
<property name="password" value="newpwd"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="dialect">org.hibernate.dialect.MySQL5Dialect</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.example.ghs.model.timetable</value>
</list>
</property>
</bean>
<bean id="baseDAO"
class="com.example.ghs.dao.BaseDAOImpl"/>
</beans>
My BaseDAO class looks like this
public class BaseDAOImpl implements BaseDAO{
private SessionFactory sessionFactory;
#Autowired
public BaseDAOImpl(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
#Override
public Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
}
The following code throws the exception in the title
public class Main {
public static void main(String[] args){
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("dao-beans.xml");
BaseDAO bd = (BaseDAO) context.getBean("baseDAO");
bd.getCurrentSession();
}
}
Does anyone have an idea about how to solve this problem?
getCurrentSession() only makes sense inside a scope of transaction.
You need to declare an appropriate transaction manager, demarcate boundaries of transaction and perform data access inside it. For example, as follows:
<bean id = "transactionManager" class = "org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name = "sessionFactory" ref = "sessionFactory" />
</bean>
.
PlatformTransactionManager ptm = context.getBean(PlatformTransactionManager.class);
TransactionTemplate tx = new TransactionTemplate(ptm);
tx.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
// Perform data access here
}
});
See also:
10. Transaction Management
13.3 Hibernate
I came across same problem and got solved as below
Added #Transactional on daoImpl class
Added trnsaction manager in configuration file:
<tx:annotation-driven/>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
I'll just add something that took me some time to debug : don't forget that a #Transactional annotation will only work on "public" methods.
I put some #Transactional on "protected" ones and got this error.
Hope it helps :)
http://docs.spring.io/spring/docs/3.1.0.M2/spring-framework-reference/html/transaction.html
Method visibility and #Transactional
When using proxies, you should apply the #Transactional annotation
only to methods with public visibility. If you do annotate protected,
private or package-visible methods with the #Transactional annotation,
no error is raised, but the annotated method does not exhibit the
configured transactional settings. Consider the use of AspectJ (see
below) if you need to annotate non-public methods.
Which package u have put the BaseDAOImpl class in.. I think It requires a package name similar to the one u have used in the application context xml and it requires a relevant annotation too.

Spring integration testing - transaction declaration in config seems to break test

I am playing around with a Spring MVC + Hibernate + MySQL "Hello World" app, and am currently trying run the following integration test on a Spring MVC controller using jUnit.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:src/main/webapp/WEB-INF/springapp-servlet.xml"})
public class InventoryControllerIT
{
#Autowired
private InventoryController controller;
#Test
public void handleRequest_anyRequest_returnsSuccessfully() throws Exception
{
ModelAndView modelAndView = this.controller.handleRequest(null, null);
}
}
However, every time I do so I get the following exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [springapp.web.InventoryController] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
Previously I hadn't implemented any real data access and the test passed fine, but now that I have added a Hibernate implementation of my DAO along with spring transaction management I get this error. Here are the relevant parts of my applet context configuration xml:
<?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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean name="/hello.htm" class="springapp.web.InventoryController">
<property name="productManager" ref="productManager" />
<property name="productDao" ref="productDao" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
...
<!-- Hibernate -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingJarLocations">
<list>
<value>WEB-INF/lib/springapp-dataaccess*.jar</value>
</list>
</property>
</bean>
<bean id="productDao" class="springapp.dataaccess.dao.ProductHibernateDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />
</beans>
If I remove the <tx:annotation-driven /> from the config then the above exception does not occur, but then the test fails because the data access call that occurs in the handler no longer has an open transaction. The app runs just fine outside of the test. Anyone have any ideas as to what the issue is?
When InventoryController implements any interfaces Spring by default applies transactional aspect to it using interface-based proxy. Such a proxy implements interfaces of InventoryController, but it's not a subclass of InventoryController, therefore it cannot be injected into a field of type InventoryController.
You either need to use interface as a type of the field to be autowired, or configure Spring to apply target-class-based proxy instead.
See also:
7.6 Proxying mechanisms
I had a similar issues while running the unit tests of a small library I was building.
replace your :
<tx:annotation-driven />
with
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
Note that on my project I also had to add the following dependency (maven project) for the unit tests:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
<scope>test</scope>
</dependency>
Best regards.

Converting XML Spring config for JMX to Java config

I have a small test app for exposing a "Bean" to JMX using Spring. It uses an XML based config and everything works fine:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.dmclaughlin.spring" />
<context:property-placeholder location="classpath:test.properties"/>
<bean id="SimpleJmxController" class="com.dmclaughlin.spring.jmx.SimpleJmxBean">
<property name="activated" value="${some.activated}"/>
</bean>
<!-- Spring JMX -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="autodetect" value="true"></property>
<property name="namingStrategy" ref="namingStrategy"></property>
<property name="assembler" ref="assembler"></property>
</bean>
<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="attributeSource"/>
</bean>
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="attributeSource"/>
</bean>
But the application I need to add this functionality to, uses #Configuration style, and I'm trying to convert the above XML to work. I added something like this:
#Bean
public MetadataNamingStrategy getNamingStrategy() {
MetadataNamingStrategy strategy = new MetadataNamingStrategy();
strategy.setAttributeSource(new AnnotationJmxAttributeSource());
return strategy;
}
#Bean
public MetadataMBeanInfoAssembler getMbeanInfoAssembler() {
return new MetadataMBeanInfoAssembler(new AnnotationJmxAttributeSource());
}
#Bean
public MBeanExporter getExporter() {
MBeanExporter exporter = new MBeanExporter();
exporter.setAutodetect(true);
exporter.setNamingStrategy(getNamingStrategy());
exporter.setAssembler(getMbeanInfoAssembler());
return exporter;
}
And everything compiles, but when I load up JConsole my Bean annotated with #ManagedResource and #ManagedAttribute isn't exposed. Am I missing something simple here?
Edit: the answer below didn't fix my problem (the problem was I was testing my XML in a Tomcat environment, but testing my non-XML config in a standalone application, which meant there was no JMXServer present.. d'oh), but it did help me simplify once I debugged what I messed up.
For me it was enough to add:
#Bean
public AnnotationMBeanExporter annotationMBeanExporter() {
return new AnnotationMBeanExporter();
}
you should configure your mbeanexporter with "eager"
#Bean
#Lazy(false)
public MBeanExporter getExporter() {
...
}
greetings
AccLess

Categories