Thursday, August 12, 2021

Spring JPA, Native Query, POJO, and Java Stream

Spring JPA can return a stream of objects in its repository API. This is a very good feature. It will help to resolve the performance issue when millions of data is returned from the database by a query. It seems to me that Spring JDBC template does not have this ability. The JdbcTemplate has a result set callback function. But that does not seem to achieve the same thing as a stream can do.

An issue with using the ORM is that the many domain data classes need to map to tables. For a legacy system, the easiest way is to use POJO and native query so that such a map is not needed. In this example application, I use POJO and native query in JPA to return a stream of the POJO object.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>spring-stream-sample</groupId>
	<artifactId>spring-stream-sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-boot-tutorial-basics</name>
	<description>Spring Boot Tutorial - Basic Concept Examples</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath />
	</parent>


	<properties>
		<hibernate.version>5.4.0.Final</hibernate.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> 
			</dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> 
			</dependency> -->

		<!-- https://mvnrepository.com/artifact/javax.persistence/javax.persistence-api -->
		<dependency>
			<groupId>javax.persistence</groupId>
			<artifactId>javax.persistence-api</artifactId>

		</dependency>


		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<version>12.2.0.1</version>
		</dependency>


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>

		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>


</project>

App.java

  package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication(scanBasePackages = { "com.sample.app" })
@ComponentScan({ "com.sample.app" })
@EnableConfigurationProperties
@EnableAutoConfiguration
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
  

SampleController.java

  package com.sample.app.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.sample.app.model.ShipmentModel;
import com.sample.app.repository.SampleRepository;

@RestController
@RequestMapping(value = "/sample")
public class SampleController {

	@PersistenceContext
	EntityManager entityManager;

	@Autowired
	private SampleRepository sampleRepository;

	@GetMapping(value = "/shipmentModel/stream/csv/{userId}")
	@Transactional(readOnly = true)
	public void generateCSVUsingStreamShipmentModel(@PathVariable("userId") long userId,
			HttpServletResponse response) {
		response.addHeader("Content-Type", "application/csv");
		response.addHeader("Content-Disposition", "attachment; filename=shipmentModel.csv");
		response.setCharacterEncoding("UTF-8");
		System.out.println("Start working. Time is " + new Date());
		try (Stream shipmentsStream = sampleRepository.findShipmentModels(userId);) {
			PrintWriter out = response.getWriter();
			// print column header
			out.write(String.join(",", ShipmentModel.headers));
			out.write("\n");
			shipmentsStream.forEach(shipmentModel -> {
				out.write(shipmentModel.toString());
				out.write("\n");
				entityManager.detach(shipmentModel);
			});
			System.out.println("Before flush. Time is " + new Date());
			out.flush();
			out.close();

			shipmentsStream.close();
			System.out.println("Stream closed. Time is " + new Date());
		} catch (IOException ix) {
			throw new RuntimeException("There is an error while downloading shipments.csv", ix);
		}

	}
}
  

ShipmentModel.java This is the POJO class.

  package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;

/*
 * Notes:
 * 1. This is a POJO. It does not map to any database table. It is annotated as an Entity. But there is no annotation @Table for it.
 * 2. The query is a pure native query. It does not use any entity mapping.
 * 3. The critical connection between the query result set and the POJO is the @CoumnResult annotation. In that annotation, the "name"
 * attribute must exactly match the column name returned by the native query. After that, the POJO uses its constructor to get the value.
 * In the constructor, the position of the argument determines what a property of the POJO will be assigned the column value. The name
 * of the property does not matter. For example, the property "shipmentLabel" of the class ShipmentModel can be any name.  This is different
 * from the entity class that maps to an actual table. In that case, there can an implicit mapping between the property name and 
 * column name of the table. For example, the camel case property name shipmentLabel will map to the table column name shipment_label.
 * 
 */

@NamedNativeQuery(name = "findShipmentModels", query = ShipmentModel.sampleSql_native, resultClass = ShipmentModel.class, resultSetMapping = "ShipmentModelMapping")

@SqlResultSetMapping(name = "ShipmentModelMapping", classes = {
		@ConstructorResult(targetClass = ShipmentModel.class, columns = { @ColumnResult(name = "id", type = Long.class),
				@ColumnResult(name = "username", type = String.class),
				@ColumnResult(name = "label", type = String.class)

		}) })

@Entity
public class ShipmentModel {

	public static String[] headers = { "userId", "userName", "label" };

	public static void main(String[] args) {
		System.out.println(String.join(",", headers));
	}

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(updatable = false)
	private Long userId;

	private String userName;

	private String shipmentLabel;

	public ShipmentModel(Long userId, String userName, String label) {
		this.userId = userId;
		this.userName = userName;
		this.shipmentLabel = label;
	}

	public Long getUserId() {
		return userId;
	}

	public void setUserId(Long userId) {
		this.userId = userId;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getShipmentLabel() {
		return shipmentLabel;
	}

	public void setShipmentLabel(String shipmentLabel) {
		this.shipmentLabel = shipmentLabel;
	}

	@Override
	public String toString() {
		return String.join(",", "" + this.getUserId(), "" + this.getUserName(), "" + this.shipmentLabel);
	}

	final static String sampleSql_native = " SELECT user.*, shipment.label "
			+ " from user JOIN shipment ON user.id = shipment.user_id " + " WHERE user.id = :userId";

}

  

SampleRepository.java

  package com.sample.app.repository;

import java.util.stream.Stream;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.sample.app.model.ShipmentModel;

public interface SampleRepository extends JpaRepository {

	@Query(nativeQuery = true, name = "findShipmentModels")
	public Stream findShipmentModels(@Param("userId") Long userId);

}

  

application.properties in main/resources

  
server.port=9123

server.servlet.context-path=/api

spring.datasource.url=jdbc:mysql://localhost:3306/planet
spring.datasource.username=zach
spring.datasource.password=planet
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.hibernate.type=trace

spring.jpa.properties.hibernate.type=trace

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true
  

create.sql in main/resources

 CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `first_name` varchar(50) DEFAULT NULL,
  `last_name` varchar(50) DEFAULT NULL,
  `gender` varchar(10) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `status` tinyint(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=10001 ;


CREATE TABLE IF NOT EXISTS `shipment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `label` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  
  FOREIGN KEY (user_id) REFERENCES user(id)
 ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=10001 ;

 INSERT INTO `user` (`id`, `username`, `first_name`, `last_name`, `gender`, `password`, `status`) VALUES
(1, 'rogers6', 'rogers', 'miller', 'M', 'psqwer', 1),
(2, 'rogpa', 'rogers', 'paul', 'M', 'dfgderet', 1),
(3, 'davm', 'david', 'merz', 'M', 'wetwet', 1),
(4, 'ma', 'mary', 'sanders', 'M', 'dfghsgh', 1),
(5, 'jhojho', 'jhonson', 'johnson', 'F', '45yhererh', 1),
(6, 'dansc', 'daniel', 'scott', 'F', 'gfndftyet', 1);


INSERT INTO `shipment` (`id`, `user_id`, `label`) VALUES
(1, '2', 'heavy box'),
(2, '1', 'interseting work'),
(3, '3', 'stones'),
(4, '3', 'computers'),
(5, '4', 'Green pepper'),
(6, '5', 'tables');

 

readme.txt


Reference Source: 
https://github.com/greyseal/spring-boot-csv-download

To run the application, the MySQL database needs to be started first. Open Windows task manager and 
go to the service tab. Find "MYSQL57" and start it. 

Question: Where in the application is it configured to use MySQL?
Answer: It is configured in the file application.properties. 

To test shipment csv download, use the following url:

http://localhost:9123/api/sample/shipmentModel/stream/csv/{userId}

In the above, {userId} is the user Id. Example:
http://localhost:9123/api/sample/shipmentModel/stream/csv/3

Thursday, December 10, 2020

Spring AOP vs AspectJ

This link to the official Spring document is very helpful: https://docs.spring.io/spring-framework/docs/2.0.x/reference/aop.html#aop-understanding-aop-proxies The following paragraph from the above document contains important technical details.

6.8.1.2. Working with multiple application contexts The AnnotationBeanConfigurerAspect used to implement the @Configurable support is an AspectJ singleton aspect. The scope of a singleton aspect is the same as the scope of static members, that is to say there is one aspect instance per classloader that defines the type. This means that if you define multiple application contexts within the same classloader hierarchy you need to consider where to define the bean and where to place spring-aspects.jar on the classpath.

Consider a typical Spring web-app configuration with a shared parent application context defining common business services and everything needed to support them, and one child application context per servlet containing definitions particular to that servlet. All of these contexts will co-exist within the same classloader hierarchy, and so the AnnotationBeanConfigurerAspect can only hold a reference to one of them. In this case we recommend defining the bean in the shared (parent) application context: this defines the services that you are likely want to inject into domain objects. A consequence is that you cannot configure domain objects with references to beans defined in the child (servlet-specific) contexts using the @Configurable mechanism (probably not something you want to do anyway!).

When deploying multiple web-apps within the same container, ensure that each web-application loads the types in spring-aspects.jar using its own classloader (for example, by placing spring-aspects.jar in 'WEB-INF/lib'). If spring-aspects.jar is only added to the container wide classpath (and hence loaded by the shared parent classloader), all web applications will share the same aspect instance which is probably not what you want.

Overview of Spring AOP

This programming style is very confusing because of the two different imlementations of AOP and two different styles of configuration. From the above link, my conclusion is summarized below.

  1. There are basically two implemenations of AOP: AspectJ and Spring AOP.
  2. There are 3 different configuration styles: Aspect language style, @AspectJ annotation style, and XML style.
  3. If choose AspectJ, you have two styles to choose: Aspect language style and @AspectJ. If choose Spring AOP, you have two styles to choose: @AspectJ and XML style.
  4. In Spring AOP, even if you choose the @AspectJ style, it does not mean that you are using the AspectJ implemenation. It is still Spring AOP. See below paragraph in the above link:

6.2.1. Enabling @AspectJ Support To use @AspectJ aspects in a Spring configuration you need to enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. By autoproxying we mean that if Spring determines that a bean is advised by one or more aspects, it will automatically generate a proxy for that bean to intercept method invocations and ensure that advice is executed as needed.

The @AspectJ support is enabled by including the following element inside your spring configuration:

<aop:aspectj-autoproxy/>

To use XML style, use

   
   <aop:config>
    .....
   </aop:config>
 

Proxy vs Code Weaving

Spring AOP is proxy-based. There are two kinds of implementation for the proxy. JDK dynamic proxy and the CGLIB proxy. If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.

You can force the use of CGLIB proxies in configuration. See the referred link.

Because Spring AOP is proxy based, there is the self-invocation issue. See "6.6.1. Understanding AOP proxies" of the linked document. AspectJ does not have this self-invocation issue.

AspectJ is not proxy-based. AspectJ uses AspectJ compiler/weaver. There are two kinds of weaving: build-time weaving and load-time weaving. The build-time weaving happens when you compile the application. The load-time weaving happens when the class is loaded into JVM. Further, for load-time weaving, there are two ways of the implemenation: (1) weaving at the JVM launch time (2) weaving when a class is loaded into JVM after JVM started.

See https://stackoverflow.com/questions/5717883/aspectj-compiler-ajc-vs-load-time-weaving#:~:text=1%20Answer&text=ajc%20(compile%20time)%20will%20only,that%20are%20affected%20by%20aspects.&text=The%20reason%20is%20that%20under,is%20not%20necessary%20under%20CTW.

Mix Spring AOP and AspectJ

What if you do want to use AspectJ instead of or in addition to Spring AOP? Section "6.8. Using AspectJ with Spring applications" of the linked document covers this.

AspectJ

AspectJ is an example of using the Java Instrument feature. This feature is a fundamentally separate from other Java programming areas. See JavaDoc https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html for more information.

To use this feature, there are two options:

  1. To specify an agent on the command line when a JVM is launched.
  2. To configure the application the JVM will use it after JVM is launched.
See https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html.

The pure AspectJ uses the first option. You need to specify the agent on the command line to start JVM.

The Spring support for AspectJ adds the second option so you can weave the Aspects when the class is loaded into JVM. This enables the applicaiton to do AspectJ on the classloader level.

An Example

Below is an example in to create an AOP class in a Spring boot application. It will set customized information when a database connection is created in Oracle.

To use this class, add the following dependency to the pom.xml file:

        <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  
The AOP class is below. It uses pure annotation.

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class ClientIdentifierConnectionPreparer {

	private static final Logger LOGGER = LoggerFactory.getLogger(ClientIdentifierConnectionPreparer.class);

	private static String CLIENT_IDENTIFIER = "-MyApplication-";
	private static String CLASS_NAME = "ClientIdentifierConnectionPreparer.CLASS";
	private static String METHOD_NAME = "afterGetConnection(JoinPoint joinPoint, Object result)";

	@AfterReturning(value = "execution(java.sql.Connection javax.sql.DataSource.getConnection(..))", returning = "result")
	public void afterGetConnection(JoinPoint joinPoint, Object result) throws SQLException {
		Connection connection = (Connection) result;
		
		String prepSql = "{ call DBMS_SESSION.SET_IDENTIFIER(?) }";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, CLIENT_IDENTIFIER);
			cs.execute();
		}

		prepSql = "{ call DBMS_APPLICATION_INFO.SET_ACTION(?)}";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, METHOD_NAME);
			cs.execute();
		}

		prepSql = "{ call DBMS_APPLICATION_INFO.SET_CLIENT_INFO(?)}";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, CLASS_NAME);
			cs.execute();
		}
	}

}
When there is a connection created, you can use the following query on Oracle to see the session with the specified client identifier.
  select * from v$session where client_identifier = '-MyApplication-'

The following query can show the actual queries used in the session.

select 
    se.sid,
    se.username,
    se.machine,
    se.seconds_in_wait,
    se.state,
    se.prev_exec_start,
    oc.cursor_type,
    trim(sql_text) sql_txt
from dual
    join v$session se
        on client_identifier = '-MyApplication-'
    join v$open_cursor oc
        on oc.sql_id = se.prev_sql_id and oc.sid = se.sid
  

Tuesday, May 29, 2018

Use Spring AOP

You can apply AOP on classes directly.
 <bean id="myService" class="com.example.MyService">
  ...
 </bean>

 <bean id="MethodHijackerAdvice" class="com.example.MethodHijacker" />

 <bean id="myAdvisor"
  class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
  <property name="mappedName" value="methodNameToBeHijacked" />
  <property name="advice" ref="MethodHijackerAdvice" />
 </bean>

 <bean
  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
   <list>
    <value>*Service</value>
   </list>
  </property>
  <property name="interceptorNames">
   <list>
    <value>myAdvisor</value>
   </list>
  </property>
 </bean>

With this configuration, all the classes whose name ends with "Service" will be hijacked by this AOP. The myService class is one of them.

MethodHijacker.java
====================
public class MethodHijacker implements MethodInterceptor
{
 @Override
 public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  System.out.println("MethodHijacker : Before method hijacked!");
  Object result = methodInvocation.proceed();
  System.out.println("MethodHijacker : After method hijacked!");
  return result;
 }
}

In the example above, the AOP proxies will be created on the classes that match the name pattern. Spring has another more powerful proxy creator DefaultAdvisorAutoProxyCreator. This one does not need to know the class names. It just creates the proxies for all the classes that match any advisor configured.

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

You can also apply AOP on interfaces.
  1. Use Spring AOP factory to create bean instance.
        
        <bean id="abstractParent" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
       <property name="interceptorNames">
        <list>
         <value>myIntroductionAdvisor</value>
        </list>
       </property>
      </bean>
    

    Note that it has the value "true" for the attribute "abstract". So this does not actually create any bean by itself.

    To actually create a bean, one example is below that makes use of the abstract bean as the parent:

       
         <bean id="myAOPbean" parent="abstractParent">
            <property name="proxyInterfaces" value="com.example.myBeanInterface" />
            <property name="target">
            <bean parent="abstractParentTarget">
               <constructor-arg value="com.example.Abc" />
            </bean>
            </property>
         </bean>
      
         <bean id="abstractParentTarget" class="com.example.CommonBehavior" abstract="true"/> 
    

    Note that com.example.myBeanInterface is an interface. This makes sense because the interface can then be proxied by AOP.

  2. In the configuration for AOP factory, specify the interceptor name (myIntroductionAdvisor in this case)
  3. The interceptor is an advisor. The example below extends the Spring Advisor DefaultIntroductionAdvisor:
       MyIntroductionAdvisor
       ===========================
        import org.springframework.aop.support.DefaultIntroductionAdvisor;
     
        public class MyIntroductionAdvisor extends DefaultIntroductionAdvisor {
     
     public MyIntroductionAdvisor() {
      super(new MyInterceptor());
     }
    
     @Override
     public boolean matches(Class clazz) {
      return MyMatcher.class.isAssignableFrom(clazz);
     }
    }
    

    In this advisor, you specify two things:
    (1) What classes will be AOP'ed. ( The MyMatcher classes )
    (2) What class will perform the AOP action. ( MyInterceptor in this case)

     
    public interface MyMatcher<T> {
     // can optionally define common behavior here. For example the method below:
     // List<T> process(String method, Object[] queryArguments);
    }  
    

    MyInterceptor can now apply behavior on the methods of all the MyMatcher classes. The interceptor API method is below.

    public Object invoke(MethodInvocation methodInvocation) throws Throwable 
    

    CommonBehavior implements the MyMatcher interface.

  4. The essential thing of the whole configuration is the interceptor that defines what you want to accomplish. The interceptor will defines what methods will be intercepted and what actions will be performed.

Thursday, April 12, 2018

Hibernate one to many relationship persistence

Introduction

The example below is based on the application https://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation. I modified it a little bit to show how to automatically save the "many" records when the "one" record is saved in a one-to-many relationship.

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/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.mkyong.common</groupId>
 <artifactId>HibernateExample</artifactId>
 <packaging>jar</packaging>
 <version>1.0</version>
 <name>HibernateExample</name>
 <url>http://maven.apache.org</url>

 <repositories>
  <repository>
   <id>JBoss repository</id>
   <url>http://repository.jboss.org/nexus/content/groups/public/</url>
  </repository>
 </repositories>
 
  <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>2.7</version>
            </plugin>           
          </plugins>
        </pluginManagement>   
    </build>

 <dependencies>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>

  <!-- MySQL database driver -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.15</version>
  </dependency>

  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>3.6.3.Final</version>
  </dependency>

  <dependency>
   <groupId>javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.12.1.GA</version>
  </dependency>

  <!-- logback logging framework-->
  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-core</artifactId>
   <version>0.9.28</version>
  </dependency>

  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>0.9.28</version>
  </dependency>


 </dependencies>
</project>

Database scripts


DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `STOCK_ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `STOCK_CODE` varchar(10) NOT NULL,
  `STOCK_NAME` varchar(20) NOT NULL,
  PRIMARY KEY (`STOCK_ID`) USING BTREE,
  UNIQUE KEY `UNI_STOCK_NAME` (`STOCK_NAME`),
  UNIQUE KEY `UNI_STOCK_ID` (`STOCK_CODE`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `mkyongdb`.`stock_daily_record`;
CREATE TABLE  `mkyongdb`.`stock_daily_record` (
  `RECORD_ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `PRICE_OPEN` float(6,2) DEFAULT NULL,
  `PRICE_CLOSE` float(6,2) DEFAULT NULL,
  `PRICE_CHANGE` float(6,2) DEFAULT NULL,
  `VOLUME` bigint(20) unsigned DEFAULT NULL,
  `DATE` date NOT NULL,
  `STOCK_ID` int(10) unsigned NOT NULL,
  PRIMARY KEY (`RECORD_ID`) USING BTREE,
  UNIQUE KEY `UNI_STOCK_DAILY_DATE` (`DATE`),
  KEY `FK_STOCK_TRANSACTION_STOCK_ID` (`STOCK_ID`),
  CONSTRAINT `FK_STOCK_TRANSACTION_STOCK_ID` FOREIGN KEY (`STOCK_ID`) 
  REFERENCES `stock` (`STOCK_ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

Domain classes

package com.mkyong.stock;

import static javax.persistence.GenerationType.IDENTITY;

import java.util.LinkedList;
import java.util.List;

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

@Entity
@Table(name = "stock", catalog = "workshop", uniqueConstraints = { @UniqueConstraint(columnNames = "STOCK_NAME"),
  @UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

 private Integer stockId;
 private String stockCode;
 private String stockName;
 private List stockDailyRecords = new LinkedList();

 public Stock() {
 }

 public Stock(String stockCode, String stockName) {
  this.stockCode = stockCode;
  this.stockName = stockName;
 }

 public Stock(String stockCode, String stockName, List stockDailyRecords) {
  this.stockCode = stockCode;
  this.stockName = stockName;
  this.stockDailyRecords = stockDailyRecords;
 }

 @Id
 @GeneratedValue(strategy = IDENTITY)
 @Column(name = "STOCK_ID", unique = true, nullable = false)
 public Integer getStockId() {
  return this.stockId;
 }

 public void setStockId(Integer stockId) {
  this.stockId = stockId;
 }

 @Column(name = "STOCK_CODE", unique = true, nullable = false, length = 10)
 public String getStockCode() {
  return this.stockCode;
 }

 public void setStockCode(String stockCode) {
  this.stockCode = stockCode;
 }

 @Column(name = "STOCK_NAME", unique = true, nullable = false, length = 20)
 public String getStockName() {
  return this.stockName;
 }

 public void setStockName(String stockName) {
  this.stockName = stockName;
 }

 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "stock")
 public List getStockDailyRecords() {
  return this.stockDailyRecords;
 }

 public void setStockDailyRecords(List stockDailyRecords) {
  this.stockDailyRecords = stockDailyRecords;
 }

}

package com.mkyong.stock;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "stock_daily_record", catalog = "workshop", uniqueConstraints = @UniqueConstraint(columnNames = "DATE"))
public class StockDailyRecord implements java.io.Serializable {

 private Integer recordId;
 private Stock stock;
 private Float priceOpen;
 private Float priceClose;
 private Float priceChange;
 private Long volume;
 private Date date;

 public StockDailyRecord() {
 }

 public StockDailyRecord(Stock stock, Date date) {
  this.stock = stock;
  this.date = date;
 }

 public StockDailyRecord(Stock stock, Float priceOpen, Float priceClose,
   Float priceChange, Long volume, Date date) {
  this.stock = stock;
  this.priceOpen = priceOpen;
  this.priceClose = priceClose;
  this.priceChange = priceChange;
  this.volume = volume;
  this.date = date;
 }

 @Id
 @GeneratedValue(strategy = IDENTITY)
 @Column(name = "RECORD_ID", unique = true, nullable = false)
 public Integer getRecordId() {
  return this.recordId;
 }

 public void setRecordId(Integer recordId) {
  this.recordId = recordId;
 }

 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "STOCK_ID", nullable = false)
 public Stock getStock() {
  return this.stock;
 }

 public void setStock(Stock stock) {
  this.stock = stock;
 }

 @Column(name = "PRICE_OPEN", precision = 6)
 public Float getPriceOpen() {
  return this.priceOpen;
 }

 public void setPriceOpen(Float priceOpen) {
  this.priceOpen = priceOpen;
 }

 @Column(name = "PRICE_CLOSE", precision = 6)
 public Float getPriceClose() {
  return this.priceClose;
 }

 public void setPriceClose(Float priceClose) {
  this.priceClose = priceClose;
 }

 @Column(name = "PRICE_CHANGE", precision = 6)
 public Float getPriceChange() {
  return this.priceChange;
 }

 public void setPriceChange(Float priceChange) {
  this.priceChange = priceChange;
 }

 @Column(name = "VOLUME")
 public Long getVolume() {
  return this.volume;
 }

 public void setVolume(Long volume) {
  this.volume = volume;
 }

 @Temporal(TemporalType.DATE)
 @Column(name = "DATE", unique = true, nullable = false, length = 10)
 public Date getDate() {
  return this.date;
 }

 public void setDate(Date date) {
  this.date = date;
 }

}


Test program

package com.mkyong;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.hibernate.Session;

import com.mkyong.stock.Stock;
import com.mkyong.stock.StockDailyRecord;
import com.mkyong.util.HibernateUtil;

/**
 * Note that this project is from
 * https://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation/
 * 
 * @author yxu
 *
 */
public class AppList {
 public static void main(String[] args) {
  System.out.println("Hibernate one to many (Annotation)");
  Session session = HibernateUtil.getSessionFactory().openSession();

  session.beginTransaction();

  Stock stock = new Stock();
  stock.setStockCode("70523");
  stock.setStockName("PADIN2");
  // session.save(stock);

  List stockDailyRecordsSet = new LinkedList();

  for (int i = 0; i < 3; i++) {
   StockDailyRecord stockDailyRecords = new StockDailyRecord();
   stockDailyRecords.setPriceOpen(new Float("1.2"));
   stockDailyRecords.setPriceClose(new Float("1.1"));
   stockDailyRecords.setPriceChange(new Float("10.0"));
   stockDailyRecords.setVolume(3000000L);
   stockDailyRecords.setDate(new Date(2018, 5, 16 + i));

   stockDailyRecords.setStock(stock);
   stockDailyRecordsSet.add(stockDailyRecords);
  }

  stock.setStockDailyRecords(stockDailyRecordsSet);

  session.save(stock);

  session.getTransaction().commit();
  System.out.println("Done");
 }
}

Run AppList, you should see that it creates the stock and the 3 StockDailyRecord records in the database.

Note that I used List as the collection type in Stock for StockDailyRecord. You can also use Set.

Note that the annotation "@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "stock")" in the class Stock is the most important in the setup. Without "casecade=CascadeType.ALL", the StockDailyRecord will not be saved into the database when the Stock is saved.

Both the tables STOCK and STOCK_DAILY_RECORD use the AUTO_INCREMENT ID as the primary key. And the Java annotation uses @GeneratedValue(strategy = IDENTITY). If a sequence is used to generate the ID, change the annotation to something like below:

 @Id
 @Column(name = "MY_ID")
 @SequenceGenerator(name = "MY_SEQ", sequenceName = "MY_SEQ", allocationSize=1)
 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="MY_SEQ")
 public Integer getMyId() {
  return this.myId;
 }

Other classes and files

package com.mkyong.util;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
 
 private static final SessionFactory sessionFactory = buildSessionFactory();
 
 private static SessionFactory buildSessionFactory() {
  try {
   // Create the SessionFactory from hibernate.cfg.xml
   return new Configuration().configure().buildSessionFactory();
  } catch (Throwable ex) {
   // Make sure you log the exception, as it might be swallowed
   System.err.println("Initial SessionFactory creation failed." + ex);
   throw new ExceptionInInitializerError(ex);
  }
 }
 
 public static SessionFactory getSessionFactory() {
  return sessionFactory;
 }
 
 public static void shutdown() {
  // Close caches and connection pools
  getSessionFactory().close();
 }
 
}
hibernate.cfg.xml
>?xml version="1.0" encoding="utf-8"?>
>!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

>hibernate-configuration>
 >session-factory>
   >property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver>/property>
  >property name="hibernate.connection.url">jdbc:mysql://localhost:3306/workshop>/property>
  >property name="hibernate.connection.username">workshop>/property>
  >property name="hibernate.connection.password">za>/property>
  >property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect>/property>
  >property name="show_sql">true>/property>
  >property name="format_sql">true>/property>
  >mapping class="com.mkyong.stock.Stock" />
  >mapping class="com.mkyong.stock.StockDailyRecord" />
 >/session-factory>
>/hibernate-configuration>

Tuesday, December 12, 2017

Debug Javascript in web page in Chrome

Chrome version used is Version 63.0.3239.84
  1. click on the 3 vertical dots at the upper right corner of the Chrome browser screen.
  2. Select "More tools" -> "Developer tools". The debugger screen will show.
  3. The debug screen has tabs at the top and the left and right panes below it.
  4. In the top bar, select "Sources"
  5. In the left pane, there is a "Network" tab. Select it. It shows a tree structure below. Drill into it to find your page URL. Sometimes you may include some javascript libraries in your page. Those js files will be under the "js" folder in that left pane. You can select it if you want to debug it.
  6. After the above step, the right pane is just blank. It does not show the html file corresponding to the page URL. You need to reload the page in order to see the HTML content. A better way is to open the Developer tools before you get to that page in your application.
  7. Now you can just click on the line number of the HTML content. Just one click is enough. The line number will be high-lighted.
  8. Now you can click button on the application web page. A new pane will show. If there is Javascript executed, it will stop at the breakpoint.
  9. The new pane has a "Watch" section. You can write your javascript expressions there to watch their values.

Monday, September 18, 2017

Spring Application Listener

How to use the Spring Application Listener

To set the application to use Spring Application listener, you can do the following.
1. In the application context file for Spring, declare a bean as follows

  <bean id="applicationEventMulticaster"
  class="org.springframework.context.event.SimpleApplicationEventMulticaster"/>
Actually, this may not needed at all. The following method is from Spring. By default, it will create a SimpleApplicationEventMulticaster if none is given.
   package org.springframework.context.support;

   public abstract class AbstractApplicationContext extends DefaultResourceLoader
  implements ConfigurableApplicationContext, DisposableBean {
   
   /**
  * Initialize the ApplicationEventMulticaster.
  * Uses SimpleApplicationEventMulticaster if none defined in the context.
  * @see org.springframework.context.event.SimpleApplicationEventMulticaster
  */
 protected void initApplicationEventMulticaster() {
  ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
   this.applicationEventMulticaster =
     beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
   if (logger.isDebugEnabled()) {
    logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
   }
  }
  else {
   this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
   beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
   if (logger.isDebugEnabled()) {
    logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
      APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
      "': using default [" + this.applicationEventMulticaster + "]");
   }
  }
 }

...
}

2. Create Application listeners
import org.springframework.context.ApplicationListener;
@Component
public class MyEventListener implements ApplicationListener {
  ...
}

import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
 ...
}


3. Just call the multicastEvent method of the bean applicationEventMulticaster when needed: applicationEventMulticaster.multicastEvent(event);

The method will invoke the application listeners to act on the event.

  public void multicastEvent(final ApplicationEvent event) {
  for (final ApplicationListener listener : getApplicationListeners(event)) {
   Executor executor = getTaskExecutor();
   if (executor != null) {
    executor.execute(new Runnable() {
     @SuppressWarnings("unchecked")
     public void run() {
      listener.onApplicationEvent(event);
     }
    });
   }
   else {
    listener.onApplicationEvent(event);
   }
  }
 }

How does the Application know what application listeners are listening?

At the application startup, the beans that implement the ApplicationListener interface will be automatically detected and registered.

ContextLoaderListener(ContextLoader).initWebApplicationContext(ServletContext)
->
XmlWebApplicationContext(AbstractApplicationContext).refresh();
->
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory). applyBeanPostProcessorsAfterIniitalization
->
AbstractApplicationContext$ApplicationListenerDetector.postProcessAfterInitialization

public Object postProcessAfterInitialization(Object bean, String beanName) {
   if (bean instanceof ApplicationListener) {
    // potentially not detected as a listener by getBeanNamesForType retrieval
    Boolean flag = this.singletonNames.get(beanName);
    if (Boolean.TRUE.equals(flag)) {
     // singleton bean (top-level or inner): register on the fly
     addApplicationListener((ApplicationListener) bean);
    }
    else if (flag == null) {
     if (logger.isWarnEnabled() && !containsBean(beanName)) {
      // inner bean with other scope - can't reliably process events
      logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
        "but is not reachable for event multicasting by its containing ApplicationContext " +
        "because it does not have singleton scope. Only top-level listener beans are allowed " +
        "to be of non-singleton scope.");
     }
     this.singletonNames.put(beanName, Boolean.FALSE);
    }
   }
   return bean;
  }

public void addApplicationListener(ApplicationListener listener) {
  if (this.applicationEventMulticaster != null) {
   this.applicationEventMulticaster.addApplicationListener(listener);
  }
  else {
   this.applicationListeners.add(listener);
  }
 }

Sunday, February 19, 2017

How to do unit test on JPA

It will be very inconvenient to test JPA DAO classes if you need to build and deploy the application to an application server. You can actually do unit tests on JPA without using any application server. The following is one example.

1. The entity class.


@Entity
@Table(name = "T_SAMPLE")
public class SampleData implements Serializable {

 private SampleDataId id;
 private String xyz;
 
 @EmbeddedId
 public SampleDataId getId() {
  return this.id;
 }

 public void setId(final SampleDataId id) {
  this.id = id;
 }


 @Column(name = "XYZ")
 public String getXyz() {
  return this.xyz;
 }

 public void setXyz(final String xyz) {
  this.xyz = xyz;
 }

}
2. The DAO class to be tested.
packge com.example.dao;

@Repository
public class SampleDataDAO {
 protected static final Logger LOG = Logger.getLogger(SampleDataDAO.class);

  @PersistenceContext
  private EntityManager entityManager;

 /**
  * Returns List of SampleData type.
  */
 public List<SampleData> getSomeData() {
   List<SampleData> results = new ArrayList<SampleData>();
   EntityManager entityManager = getEntityManager();
   final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
   final CriteriaQuery<SampleData> query = builder
     .createQuery(SampleData.class);
   final Root<SampleData> root = query.from(SampleData.class);
   List<Predicate> predicates = new ArrayList<Predicate>();
   query.select(root);
    ......
   query.where(predicates.toArray(new Predicate[] {}));

   results = entityManager.createQuery(query).getResultList();
   return results;
 }

public void saveSomeData(SampleData sampleData) {
   EntityManager entityManager = getEntityManager();
   entityManager.persist(sampleData); 
 }
}
3. The test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/testApplicationContext.xml" })
public class SampleDataDAOTest {

 @Autowired
 private SampleDataDAO sampleDataDAO;

 @Test
 public void testRead() {
  sampleDataDAO.getSomeData(...);
 }

 @Test
 @Transactional
 public SampleData testSave() {
  SampleData sampleData = new SampleData();
  sampleData.setXyz("bla");
  sampleDataDAO.saveSomeData(sampleData);
  SampleData savedData = sampleDataDAO.getSampleDataById(sampleData.getId());
  Assert.assertNotNull(savedData);
  return savedData;
 }
}

Notes:

  1. The method testSave() has the annotation @Transactional. Without this annotation, the test won't work. The savedData will be null. The @Transactional annotation has to appear somewhere. If the method saveSomeData of sampleDataDAO is annotated with @Transactional, then it is also fine.
  2. The good thing about using @Transactional on the method of a Junit test class is that Spring will automatically rollback the transaction after the test is done. So the above test method testSave() won't actually create a record in the database after the test. Below is from Spring documentation.

    In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener. Note that TransactionalTestExecutionListener is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must provide a PlatformTransactionManager bean in the application context loaded by @ContextConfiguration semantics. In addition, you must declare @Transactional either at the class or method level for your tests.

4. The configuration file app/src/test/resources/testApplicationContext.xml.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
    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-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@myurl"
        p:username="myUsername" p:password="myPassword" p:initialSize="5" p:maxActive="10">
    </bean>
 
    <bean
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        id="entityManagerFactory">
        <property name="dataSource" ref="dataSource" />
    </bean>

 
    <context:component-scan base-package="com.example.dao">
    </context:component-scan>
 
    <bean class="org.springframework.orm.jpa.JpaTransactionManager"
        id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
 
   
    <context:spring-configured />
    <context:annotation-config />
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>
5. The file app/src/main/java/META-INF/persistence.xml:
   <persistence xmlns="http://java.sun.com/xml/ns/persistence"
 version="1.0">

 <persistence-unit name="MY-UNIT" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <non-jta-data-source>dataSource</non-jta-data-source>
  <properties>
   <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
   <!-- property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/ -->
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.hbm2ddl.auto" value="none" />
   <prop key="hibernate.connection.autocommit">false</prop>
   <prop key="hibernate.connection.release_mode">after_transaction</prop>
  </properties>
 </persistence-unit>

</persistence> 

Note that the output log will show that this persistence unit is used.

Question. The test needs to use the Entity class SampleData.java. How is this class discovered in the test context?

Answer: The document http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/configuration.html#setup-configuration-bootstrapping has good explaintion. It seems that by default, JPA will discover all the classes annotated with @Entity in the archive in the bootstrap process.

2.2.1. Packaging

The configuration for entity managers both inside an application server and in a standalone application reside in a persistence archive. 
A persistence archive is a JAR file which must define a persistence.xml file that resides in the META-INF folder. 
All properly annotated classes included in the archive (ie. having an @Entity annotation), all annotated packages and all 
Hibernate hbm.xml files included in the archive will be added to the persistence unit configuration, so by default, 
your persistence.xml will be quite minimalist:


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="sample">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

The actual command line of the test run is something like below:

C:\tools\jre-7u79-windows-x64\jre1.7.0_79\bin\javaw.exe -ea 
-classpath C:\Users\me\workspace\app\target\test-classes;
C:\Users\me\workspace\app\target\classes;
......
 -testLoaderClass org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader 
-loaderpluginname org.eclipse.jdt.junit4.runtime 
-classNames com.sample.dao.SampleDataDAOTest
Debugging of the program shows the following details. 1. The file testApplicationContext.xml has the bean entityManagerFactory of the class org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean. 2. When this bean is created, its method afterPropertiesSet() calls HibernatePersistence.createContainerEntityManagerFactory(), which calls Ejb3Configuration.buildEntityManagerFactory(), and so on. Eventually it will create a map that contains all the classes with the annotation @Entity. The last few classes used in this chain of call are JPA or Hibernate classes which are not related to Spring. So these classes must have the builtin ability to discover the @Entity classes by following the JPA standards.

Note that if you just use Hibernate without using the generic JPA,then you use the configuration file hibernate.cfg.xml. And you can list the entity classes or packages in that file as the following example shows.

  <hibernate-configuration>
     <session-factory>
       <property name="hibernate.connection.driver_class>"com.mysql.mysql.Driver</property>
       ......
       <mapping class="com.sample.SampleData"/>
     </session-factory>
   </hibernate-configuration>