Thursday, June 18, 2015

How to set development/test/production environment in spring project (XML)

In real life development, a project always need different sets of  configurations for development, system test and production environments. Most common variables in these different environments are database location or jms location.

If the project uses spring and will not run in any container, the spring profiles can be used to make your life easier when switching environments. This article demonstrates the best practice to do so in my experiences to achieve these goals:

  1. Finally there will be only one packaged jar for all different environment
  2. The same jar can run in development environment by giving option  "-Dspring.profiles.active=development" to JVM
  3. The same jar can run in test environment by giving option  "-Dspring.profiles.active=test" to JVM
  4. The same jar can run in production environment without options to JVM
  5. The JUnit test cases can easily switch between different environments

If your project will run within web containers, should use JNDI to define the resources and inject resources into Java code.

The basic idea is put property variables in 3 different property files, namely development.properties/test.properties/production.properties, then use the proper file for different environment ( spring active profile).

This tutorial uses spring XML config. For tutorial using spring Java config, see here.

In this demo, there are 2 beans, a data source which connecting to different DB, and a hello world print out different string in different environment.

0. What you need

  • JDK 1.7+
  • Maven 3.2.1
  • Spring 4.1.0.RELEASE

1. Maven pom.xml

<project 
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.shengwang.demo</groupId>
<artifactId>spring-use-profiles-xml</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-use-profiles-xml</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>

<!-- Database -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>

<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>


<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.0.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

</dependencies>

<!-- Use Java 1.7 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

This dependencies in this demo can be divided into 3 groups, Spring, Database connection and Unit test.


2. Define Java class


There are 2 classes. The first one is a simple hello world bean, which use a property value to say hello to.

package com.shengwang.demo;

import org.springframework.stereotype.Component;

@Component
public class HelloWorldBean {
String name;

public HelloWorldBean(String name) {
this.name = name;
}

public String sayHello() {
return "Hello "+name;
}
}

The second class is the main class.

package com.shengwang.demo;

import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class DemoMain {

public static void main(String[] args) {

// Initial spring context with profiles
GenericXmlApplicationContext ctx= new GenericXmlApplicationContext();
ConfigurableEnvironment env = (ConfigurableEnvironment) ctx.getEnvironment();
env.setDefaultProfiles("production");
ctx.load("spring-context.xml");
ctx.refresh();

HelloWorldBean helloWorld = ctx.getBean(HelloWorldBean.class);
System.out.println(helloWorld.sayHello());
}
}

In the main, spring context is created from xml configuration file. Notice that the default active profile sets to "production", so if there is no option to set active profile when running, the program  run in production environment.


3. Spring configuration


Spring configuration file spriing-context.xml, is the key of this article.

<?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:beans="http://www.springframework.org/schema/beans"
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">

<!-- ======================================= -->
<!-- use varibles in bean definition -->
<!-- ======================================= -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<bean class="com.shengwang.demo.HelloWorldBean">
<constructor-arg value="${hellow.world.name}" />
</bean>


<!-- ====================================================== -->
<!-- import different variables according to active profile -->
<!-- ====================================================== -->
<beans profile="development">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/development.properties" />
</beans>

<beans profile="test">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/test.properties" />
</beans>

<beans profile="production">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/production.properties" />
</beans>

</beans>

Beside the xml header, the spring xml configuration mainly has 2 parts. The first part define 2 beans, a dataSource and a helloworldBean. The point need to mention is the placeholders, looks like ${…}, used as values in bean definitions. Where are the placeholders defined? In the property files that are imported in the second part of the configuration.  There are 3 property files represents  development/test/production environments respectively.


4. Property file


Property files are plain text files. Line starts with '#' is comment. There are 3 property files: development.properties, test.properties, production.properties.  Three files have same properties but with different values.  Only 2 are listed here to save space.


image 


image


Now the whole project directory hierarchy looks like.


image  


5. Specify profile during development


As beginning mentions, in development, set JVM options -Dspring.profiles.active=development in your IDE or command line.  For example in Eclipse, this can be done using menu Run->Run Configuration. This JVM option only need to be specified for run the project. There is no need to specify this JVM option for JUNIT test.


image


Run the project and we can see it's using the development properties.


image


6. Specify profile for Junit


Add @ActiveProfiles in the junit test to specify the profile. There is NO need to specify the "-Dspring.profiles.active" JVM option for JUNIT test.

package com.shengwang.demo;

import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-context.xml")
@ActiveProfiles("development")
public class HelloWorldBeanTest {

@Autowired HelloWorldBean helloWorld;

@Test public void testSayHello() {
assertEquals("Hello development env",helloWorld.sayHello());
}
}

Hope this article can make your life easier!

Monday, June 15, 2015

Spring JMS with ActiveMQ – hello world example – receive message using annotation

Since spring 4.1, using spring jms  to receive message can even easier than before. Just add one annotation @JmsListener before any POJO’s method. In this article, a hello world example will show how easy it can be done.

There are other ways to  receive message from ActiveMQ broker using Spring JMS, examples are provided here

The example on how to send out message with Spring JMS and ActiveMQ is in this article.

0. What you need

  • JDK 1.7+
  • Maven 3.2.1
  • ActiveMQ 5.10.0
  • Spring 4.1.0.RELEASE

In this example  we'll run the ActiveMQ broker on a machine of IP 192.168.203.143 with default port 61616.

1. Configure maven pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>spring-jms-activemq-receive-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
<!-- Spring version -->
<spring-framework.version>4.1.0.RELEASE</spring-framework.version>
<!-- ActiveMQ version -->
<activemq.version>5.10.0</activemq.version>
</properties>

<dependencies>
<!-- Spring Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring-framework.version}</version>
</dependency>

<!-- ActiveMQ Artifact -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-spring</artifactId>
<version>${activemq.version}</version>
</dependency>
</dependencies>

<!-- Use Jave 1.7 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

There are 2 dependencies, spring-jms and activemq-spring. JDK version is specified to 1.7


2. Define Java class


There are 2 classes in this demo. The first one is a simple spring bean.

package com.shengwang.demo;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Service;

@Service
public class JmsMessageListener {

@JmsListener(destination="SendToRecv")
@SendTo("RecvToSend")
public String processMessage(String text) {
System.out.println("Received: " + text);
return "ACK from handleMessage";
}
}

The only thing make this class different from a common spring bean is the @JmsListener and @SendTo annotations before the method. The @JmsListener tell spring context the following method will be used whenever a message is coming from the destination "SendToRecv" asynchronously. The @SendTo annotation specify the destination for return message from the method.


The second class is the main class.

package com.shengwang.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoMain {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("app-context.xml");
}
}

There's only one line in the main to create the spring context. Imagine what will happen when running it. Will the main exit immediately after the line? No, Because the control is in Spring context, which is waiting for incoming messages from JMS. That's the feeling of Inversion of Control (IoC).  Let the context choose what to do.


3. Spring configuration


In the Spring configuration app-context.xml, there are 3 beans need to define:


  • ActiveMQ connection factory
  • Spring connection factory (use the above one)
  • Spring jms listener container factory (use the above one)
<?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:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd
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">


<context:component-scan base-package="com.shengwang.demo" />

<!-- enable the configuration of jms on annotations -->
<jms:annotation-driven/>

<!-- =============================================== -->
<!-- JMS Common, define JMS connectionFactory -->
<!-- =============================================== -->
<!-- Activemq connection factory -->
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<!-- brokerURL -->
<constructor-arg index="0" value="tcp://192.168.202.168:61616" />
</bean>

<!-- Pooled Spring connection factory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg ref="amqConnectionFactory" />
</bean>

<!-- =============================================== -->
<!-- JMS receive, define JmsListenerContainerFactory -->
<!-- =============================================== -->
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory" />
<property name="concurrency" value="3-10"/>
</bean>

</beans>

There's a concurrency parameter for the bean jmsListenerContainerFactory, which is 3-10 here. It means at lease 3 beans instances are waiting for the incoming message. It can process 10 incoming messages in parallel.


Everything is almost done now. Let's review the directory structure for our maven project.


image


4. Run the code


Before you can run the code, you need to make sure the ActiveMQ broker is running. So our jave code can connect to it and receive message from it.   Make sure the broker IP and port are correct in your spring configuration file "app-context.xml". Run the ActiveMQ broker like with this command.

ACTIVEMQ_INSTALL_DIR/bin/activemq start

Now you can run you main class. you can run it from IDE such as eclipse, or you can run it direct in command line by using maven.

cd spring-jms-activemq-receive-annotation
# run mvn from project directory
mvn exec:java -Dexec.mainClass="com.shengwang.demo.DemoMain"

After running the demo, Let's check from ActiveMQ Web Console. (http://activemqIp:8161)


image


Now send a message to the Queue "SendToRecv", and refresh the browser.


image


Check the reply message. The return of the listener method will be send back as JMS message automatically.


image

Sunday, June 14, 2015

Build a spring project into an executable standalone jar in maven

To create an executable standalone jar, which can be run by command  like java –jar myExecutable.jar, there are 2 conditions need to be fulfilled:

  • Specify the entry main class in META-INFO/MANIFEST.MF file
  • Include all dependences  in the final jar file

The second condition is not needed theoretically, but in practical will always be necessary.

If the project uses Spring with xml configuration, then there’s one more condition to fulfill.

  • Handle spring schemas for different spring packages. (see below for explanation)

The best maven plugin to handle all there three requirements are maven-shade-plugin. (some other plugin such as maven-assembly-plugin can deal with the first two requirements, but not the third one).

In this article, a hello world spring project will be package to an executable standalone jar file using maven. The way used in this article can also perfectly create executable standalone jar package, even if the project don’t use spring.

0. What you need

  • JDK
  • Spring
  • Maven 3.2.1

1. Define the class

There are 2 classes. The first one the a spring bean.

package com.shengwang.demo;

import org.springframework.stereotype.Component;

@Component
public class HelloWorldBean {
public void sayHello(String name) {
System.out.println("Hello "+name);
}
}

The second one is the main class, which get the hello world bean and call its sayHello method.

package com.shengwang.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoMain {

public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("application-context.xml");
HelloWorldBean bean = ctx.getBean(HelloWorldBean.class);

bean.sayHello("Spring");

((ClassPathXmlApplicationContext) (ctx)).close();
}
}

2. Spring configuration


The spring xml configuration file application-context.xml is also very simple.

<?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:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">

<context:component-scan base-package="com.shengwang.demo" />

</beans>

3. Maven pom


Plugin maven-shade-plugin in the pom file is the key of this demo.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.shengwang.demo</groupId>
<artifactId>spring-executable-standalone</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-executable-standalone</name>
<url>http://maven.apache.org</url>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
</dependencies>

<build>

<plugins>
<!-- Use JDK 7 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>

<!-- =============================================================== -->
<!-- use shade plugin to package spring project into executable jar -->
<!-- =============================================================== -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- =================================================== -->
<!-- define the main entry for the output jar file -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.shengwang.demo.DemoMain</mainClass>
</transformer>
<!-- =================================================== -->
<!-- append all spring.handlers instead of overwriting -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<!-- =================================================== -->
<!-- append all spring.schemas instead of overwriting -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
<!-- ============================================================ -->
<!-- exclude any digital signiture file from third party jar file -->
<!-- ============================================================ -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>

</plugins>
</build>
</project>

In the plugin, 3 transformers and 1 filter are used. Let’s explain them one by one.


The first transformer is ManifestResourceTransformer, which will modify the META-INFO/MANIFEST.MF file in the final  output jar file. This transformer is used to add Main-Class entry in the MANIFEST.MF file. If unzip the output jar, open the META-INFO/MANIFEST.MF file, we can see the added line.


image


The second and third transformers are AppendingTransformer. Why these two transformers are needed? Because every Spring package store xsd mapping file in the same direcotry hierarchy, /META-INF/spring.schemas and /META-INF/spring.handlers, see example below.


image image


For example two spring artifacts, spring-aop and spring-beans, have the exactly same files in the same directory. When using maven to package jar, these files from different dependency packages are combined in to one file by the transformers.


The filters are used to exclude the digital signature files out of the final output jar file. Some of the third party dependency may be digitally signed. These signature files must be screened out to prevent signature verification exceptions.


For most cases, the main-class entry is the only thing need to change.


4. Package


Run maven to package the project into one jar file.

mvn clean package

5. Run the output jar


Run the output jar file

java -jar spring-executable-standalone-0.0.1-SNAPSHOT.jar

The output looks like:


image

Friday, June 12, 2015

Use Hibernate level two cache with ehcache – hello world example

No doubt that well designed second level cache will greatly increase persistence performance. Hibernate clearly define the usage of 2nd level cache, but leave the cache implementation to other cache providers.  In this article ehcache is used as the second level cache provider.

0. What you need

  • JDK 1.7+
  • Spring 4.1.0.RELEASE
  • Hibernate 4.3.4.Final

1. Configure maven pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.shengwang.demo</groupId>
<artifactId>spring-persistence-hibernate-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-persistence-hibernate-cache</name>
<url>http://maven.apache.org</url>

<properties>
<!-- Spring version -->
<spring.version>4.1.0.RELEASE</spring.version>

<!-- Hibernate version -->
<hibernate.version>4.3.4.Final</hibernate.version>
</properties>

<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>

<!-- Database driver-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>

</dependencies>

<!-- Use java 1.7 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

The dependencies in pom file belong to spring, hibernate and database connection driver.  Beside the normal needed dependencies, the  hibernate-ehcache is added to serve as 2nd level cache provider, which has the same version as the hibernate-core.


2. Add @Cache to entity class


In this demo, there are 2 entity classes, Client and PurchaseOrder. One client can has many orders, so it’s a one-to-many relation.


First is the PurchaseOrder entity class.

package com.shengwang.demo.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;


@Entity
@Table(name="purchase_order")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class PurchaseOrder {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="order_id")
private int orderId;

@Column(name="order_desc")
private String orderDesc;

@ManyToOne(cascade=CascadeType.PERSIST)
@JoinColumn(name="client_id")
private Client client;

public int getOrderId() {
return orderId;
}

public String getOrderDesc() {
return orderDesc;
}

public Client getClient() {
return client;
}

public void setOrderId(int orderId) {
this.orderId = orderId;
}

public void setOrderDesc(String orderDesc) {
this.orderDesc = orderDesc;
}

public void setClient(Client client) {
this.client = client;
}
}

This is just an ordinary entity class, but with just one extra annotation @Cache(usage=CacheConcurrencyStrategy.READ_ONLY) . That tells the hibernate the entity can be cached.


Then the client entity class.

package com.shengwang.demo.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;


@Entity
@Table(name="client")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class Client {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="client_id")
private int clientId;

@Column(name="client_name")
private String clientName;

//------------------------------------
// Caution: need to cache collections
//------------------------------------
@OneToMany(mappedBy="client",cascade=CascadeType.PERSIST)
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
private Set orders = new HashSet();


public int getClientId() {
return clientId;
}
public String getClientName() {
return clientName;
}

public Set getPurchaseOrders() {
return orders;
}
public void setClientId(int clientId) {
this.clientId = clientId;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public void setPurchaseOrders(Set orders) {
this.orders = orders;
}
}

Compare to the PurchaseOrder entity class, the Client entity not only add @Cache annotation before the class definition like the above one, but also add @Cache annotaion before the collections elements. Because by default the collection fields of the entity are not cached. For most of the time collections do need to be cached to get performance improvement.


3. Configure Spring


In spring configuration file, enable the second level cache and setup the ehcahe as the cache provider.

<?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:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

<!-- scan for all spring beans -->
<context:component-scan base-package="com.shengwang.demo" />

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- persistence -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring" />
<property name="username" value="root" />
<property name="password" value="IHave1Dream!" />
</bean>

<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>

<!-- enable 2nd level cache -->
<prop key="hibernate.cache.use_second_level_cache">true</prop>

<!-- setup 2nd level cache -->
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory </prop>
<prop key="net.sf.ehcache.configurationResourceName">/ehcache.xml</prop>

</props>
</property>
<!-- set auto scan the Entity, otherwise you will get 'Unknown entiy' error -->
<property name="packagesToScan" value="com.shengwang.demo.model" />
</bean>

<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory">
</bean>

</beans>

In the session factory bean configuration, hibernate.cache.use_second_level_cache set to true to enable 2nd level cache. The cache providers set to ehcache using hibernate.cache.region.factory_class property. Also the ehcache configuration file set to ehcache.xml, which will be shown next.


4. Configure ehcache


The ehcache.xml is ehcache configuration file, which looks like this.

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">

<cache name="demoEntityCache"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>

<defaultCache maxEntriesLocalHeap="0" eternal="false" timeToIdleSeconds="1200" timeToLiveSeconds="1200">
</defaultCache>
</ehcache>

Now the hibernate level 2 cache is ready to serve.  In the next article here, several persistence operation will be made to verify the level two cache’s effect.

Thursday, June 11, 2015

Ehcache Issue : Element <cache> does not allow attribute "maxEntriesLocalHeap"

When using Ehcache as Hibernate second level cache provider, You may face the org.xml.sax.SAXException like this:

Caused by: org.xml.sax.SAXException: null:25: Element <cache> does not allow attribute "maxEntriesLocalHeap".

Reason

This is because in “maxEntriesLocalHeap” is used in ehcahe configuration file ehcache.xml. “maxEntriesLocalHeap” is introducted to ehcache from 2.5, so if the actually ehcache-core version in you project is less then 2.5, the excpetion above comes out.

Unfortunately, Hibernate 4.3 release still uses ehcache 2.4.3. So if the project pom.xml just like this, the exception occures.

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.4.Final</version>
</dependency>

image


Solution


To solve this, just add explicit dependency to you project.

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.4.Final</version>
</dependency>

<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.5</version>
</dependency>

Now the dependency looks like this, the SAXException is gone.


image

Tuesday, June 9, 2015

Use JMX to monitor Hibernate 4.3 statistics (with Spring)

To find out hibernate cache’s performance, like cache hit/miss ratio, JMX can be convenient. Before Hibernate 4.3, there is a StatisticsService class can be used. But Hibernate 4.3 removed this class.  If the project also uses Spring framework, it can still be done. 

The original solution came from Marcel Stor from here. This article try to compliment it with details.

0. What you need

  • JDK1.7 +
  • Maven 3.2.1+
  • Spring 4.1.0.RELEASE
  • Hibernate 4.3.4.Final

1. Define Java class

One Jave class is needed. Add the following class to the project.  The class is defined as a normal Spring bean with @Component annotation.

package com.shengwang.demo.statistics;

import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class HibernateStatisticsFactoryBean implements FactoryBean<Statistics> {

@Autowired
private SessionFactory sessionFactory;

@Override
public Statistics getObject() throws Exception {
return sessionFactory.getStatistics();
}

@Override
public Class<?> getObjectType() {
return Statistics.class;
}

@Override
public boolean isSingleton() {
return true;
}
}

2. Configure Spring


Configure Spring, add following to the spring configuration xml file.

<!-- ==================================== -->
<!-- Hibernate 4.3 Statistics for JMX -->
<!-- ==================================== -->
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="Hibernate:type=statistics">
<ref bean="hibernateStatisticsFactoryBean"/>
</entry>
</map>
</property>
</bean>

The bean hibernateStatisticsFactoryBean used is the Class defined above. 


All done!


3. Check from JConsole


Run JConsole, the JMX tool shipped with JDK, connect to the running project. Now hibernate statistics can be monitored!


Connect to the Java application process.

image

Now the Hibernate performance can be monitored by JMX, for example the 2nd level cache hit ratio in the following snapshot.


image
Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List