Thursday, August 14, 2014

Table Generator in JPA

use a self-defined table for identity generation on any database, which means it is portable across databases.

1) firstly, create a table to store the id will be generated.

create table tb_id_generator (
tb_name varchar(50) not null primary key,
tb_id int(20) not null
);

insert into tb_id_generator(tb_name,tb_id) values ('table1',1);
insert into tb_id_generator(tb_name,tb_id) values ('table2',1000000);

mysql> select * from tb_id_generator;
+---------+---------+
| tb_name | tb_id   |
+---------+---------+
| table1  |           1  |
| table2  | 1000000 |
+---------+---------+
2 rows in set (0.00 sec)


2) specify strategy in entity object.

------------- PersonEO -------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person")
public class PersonEO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen")
@TableGenerator(name = "person_id_gen", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table1")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

person_id_gen: the name of stragety
tb_id_generator: the table name to store id
table1:  to generate id for entity PersonEO

--------------- Person2EO--------------

package wx.dbdemoc.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

@Entity
@Table(name = "person2")
public class Person2EO implements Serializable {

private static final long serialVersionUID = 1492053620157953609L;
private Long pId;
private String name;

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "person_id_gen2")
@TableGenerator(name = "person_id_gen2", table = "tb_id_generator", pkColumnName = "TB_NAME", valueColumnName = "TB_ID", pkColumnValue = "table2")
public Long getpId() {
return pId;
}

public void setpId(Long pId) {
this.pId = pId;
}

@Column(name = "name")
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

3)  use multiple data source, and multiple threads in single data source to run it, found it is working well in multiple threads

spring + rabbitmq + message-driven consumer

As a message consumer, the way to retrieve the messages from queue via either poll or push.

The is an easy sample how to receive messages via message driven approach, so call "push"

1 app-config.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:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">


<rabbit:admin connection-factory="rabbitConnectionFactory" />

<rabbit:connection-factory id="rabbitConnectionFactory"
host="10.10.1.197" port="5672" channel-cache-size="1" username="tester1"
password="tester1" />

<rabbit:queue id="myqueue" name="test.queue1" />

<rabbit:listener-container
connection-factory="rabbitConnectionFactory" concurrency="1" prefetch="1">
<rabbit:listener queue-names="test.queue1" ref="myListener" />
</rabbit:listener-container>

<bean id="myListener" class="wx.mqdemo.MyListener" />
</beans>

2. message listener class

package wx.mqdemo;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class MyListener implements MessageListener {

public void onMessage(Message msg) {
byte[] body = msg.getBody();
System.out.println(new String(body));
// if want to roll-back message, throw runtime exception
// throw new RuntimeException("want to rollback tx");
}
}

3. java main

package wx.mqdemo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LaunchIt {

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

}


---------------
in order to run this sample, need to prepare a queue named "test.queue1" in rabbitmq, and create a user "tester1"


Thursday, August 7, 2014

spring batch admin - monitor batch jobs

web site : http://docs.spring.io/spring-batch-admin/index.html
download url: http://docs.spring.io/downloads/nightly/release-download.php?project=BATCHADM

decompress the latest version spring-batch-admin-1.3.0.RELEASE.zip

go to the sample folder \spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample and run mvn clean install

go to spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample\target\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\classes\META-INF
remove all sub-folders to remove the sample jobs, such as job1,job2 and infinity

go to spring-batch-admin-1.3.0.RELEASE\sample\spring-batch-admin-sample\target\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\classes
remove \org and its all sub-folders to remove the servlets for sample jobs, such as job1,job2 and infinity

remove business-schema-mysql.sql and batch-hsql.properties

copy all the content in batch-mysql.properties to batch-default.properties,then remove file batch-default.properties, and edit jdbc configuraiton to customize your env.

TAKE NOTE:
batch.data.source.init=false
set it to false if batch db and tables are created in advance, otherwise this property=true will drop db first, then create new tables which means all historical data in db will be lost.

copy folder \target\spring-batch-admin-1.3.0.RELEASE to tomecat

copy mysql driver jar to folder %TOMCAT%webapps\spring-batch-admin-sample-1.3.0.RELEASE\WEB-INF\lib

launch tomcat, and go to this url with browse http://localhost:8080/spring-batch-admin-sample-1.3.0.RELEASE/jobs

initially, there is not any jobs, even sample jobs










launch the batch jobs described here













Tuesday, August 5, 2014

configure spring batch as window service

The target to configure this spring batch job demo as window service to run and manage.

1) download java service wrapper from http://wrapper.tanukisoftware.com/doc/english/download.jsp and decompress the zip file

2) use the method3 for integration, refer to http://wrapper.tanukisoftware.com/doc/english/integrate-listener.html

3) in order to integrate the service wrapper, we need a Main class

package wx.batch1;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.tanukisoftware.wrapper.WrapperListener;
import org.tanukisoftware.wrapper.WrapperManager;

public class MyWrapper implements WrapperListener {

private ClassPathXmlApplicationContext context;

/*---------------------------------------------------------------
* Constructors
*-------------------------------------------------------------*/
private MyWrapper() {
}

/**
* Called whenever the native Wrapper code traps a system control signal
* against the Java process. It is up to the callback to take any actions
* necessary. Possible values are: WrapperManager.WRAPPER_CTRL_C_EVENT,
* WRAPPER_CTRL_CLOSE_EVENT, WRAPPER_CTRL_LOGOFF_EVENT, or
* WRAPPER_CTRL_SHUTDOWN_EVENT
*
* @param event
*            The system control signal.
*/
public void controlEvent(int event) {
if ((event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT)
&& (WrapperManager.isLaunchedAsService() || WrapperManager
.isIgnoreUserLogoffs())) {
// Ignore
} else {
WrapperManager.stop(0);
// Will not get here.
}

}

/*---------------------------------------------------------------
* WrapperListener Methods
*-------------------------------------------------------------*/
/**
* The start method is called when the WrapperManager is signaled by the
* native Wrapper code that it can start its application. This method call
* is expected to return, so a new thread should be launched if necessary.
*
* @param args
*            List of arguments used to initialize the application.
*
* @return Any error code if the application should exit on completion of
*         the start method. If there were no problems then this method
*         should return null.
*/
public Integer start(String[] arg) {

// load spring configure file and launch it
context = new ClassPathXmlApplicationContext("classpath:app-context.xml");
context.start();
WrapperManager.signalStarting(30000);
return null;
}

/**
* Called when the application is shutting down. The Wrapper assumes that
* this method will return fairly quickly. If the shutdown code code could
* potentially take a long time, then WrapperManager.signalStopping() should
* be called to extend the timeout period. If for some reason, the stop
* method can not return, then it must call WrapperManager.stopped() to
* avoid warning messages from the Wrapper.
*
* @param exitCode
*            The suggested exit code that will be returned to the OS when
*            the JVM exits.
*
* @return The exit code to actually return to the OS. In most cases, this
*         should just be the value of exitCode, however the user code has
*         the option of changing the exit code if there are any problems
*         during shutdown.
*/
public int stop(int exitCode) {
// shutdown spring context
context.registerShutdownHook();

WrapperManager.signalStopping(60000);
return exitCode;
}

/*---------------------------------------------------------------
* Main Method
*-------------------------------------------------------------*/
public static void main(String[] args) {
// Start the application. If the JVM was launched from the native
// Wrapper then the application will wait for the native Wrapper to
// call the application's start method. Otherwise the start method
// will be called immediately.
WrapperManager.start(new MyWrapper(), args);
}
}

4) compile above main class with demo together and generate a jar file batch1.jar

5) create a folder structure as follows,

/bin - copy from step 1
/con
  | - wrapper.conf    //-- copy from step 1
  |- app-context.xml
  |- spring-batch1.xml
/lib
  |- batch1.jar
  |- wrapper.jar   // copy from step 1
  |- wrapper.dll   // copy from step 1
  |- spring-batch-core-2.2.7.RELEASE.jar
  ...... all the other dependencies
/logs   

6) open the wrapper.conf and edit it (the changes are highlighted in red)

#encoding=UTF-8
# Configuration files must begin with a line specifying the encoding
#  of the the file.

#********************************************************************
# Wrapper License Properties (Ignored by Community Edition)
#********************************************************************
# Professional and Standard Editions of the Wrapper require a valid
#  License Key to start.  Licenses can be purchased or a trial license
#  requested on the following pages:
# http://wrapper.tanukisoftware.com/purchase
# http://wrapper.tanukisoftware.com/trial

# Include file problems can be debugged by removing the first '#'
#  from the following line:
##include.debug

# The Wrapper will look for either of the following optional files for a
#  valid License Key.  License Key properties can optionally be included
#  directly in this configuration file.
#include ../conf/wrapper-license.conf
#include ../conf/wrapper-license-%WRAPPER_HOST_NAME%.conf

# The following property will output information about which License Key(s)
#  are being found, and can aid in resolving any licensing problems.
#wrapper.license.debug=TRUE

#********************************************************************
# Wrapper Localization
#********************************************************************
# Specify the locale which the Wrapper should use.  By default the system
#  locale is used.
#wrapper.lang=en_US # en_US or ja_JP

# Specify the location of the Wrapper's language resources.  If these are
#  missing, the Wrapper will default to the en_US locale.
wrapper.lang.folder=../lang

#********************************************************************
# Wrapper Java Properties
#********************************************************************
# Java Application
#  Locate the java binary on the system PATH:
wrapper.java.command=C:/jdk1.6.0_45/bin/java
#  Specify a specific java binary:
#set.JAVA_HOME=/java/path
#wrapper.java.command=%JAVA_HOME%/bin/java

# Tell the Wrapper to log the full generated Java command line.
#wrapper.java.command.loglevel=INFO

# Java Main class.  This class must implement the WrapperListener interface
#  or guarantee that the WrapperManager class is initialized.  Helper
#  classes are provided to do this for you.  See the Integration section
#  of the documentation for details.
wrapper.java.mainclass=wx.batch1.MyWrapper

# Java Classpath (include wrapper.jar)  Add class path elements as
#  needed starting from 1
wrapper.java.classpath.1=../conf
wrapper.java.classpath.2=../lib/wrapper.jar
wrapper.java.classpath.3=../lib/mysql-connector-java-5.1.30.jar
wrapper.java.classpath.4=../lib/spring-batch-core-2.2.7.RELEASE.jar
wrapper.java.classpath.5=../lib/spring-core-3.2.10.RELEASE.jar
wrapper.java.classpath.6=../lib/spring-jdbc-3.2.10.RELEASE.jar
wrapper.java.classpath.7=../lib/batch1.jar
wrapper.java.classpath.8=../lib/spring-context-3.2.10.RELEASE.jar
wrapper.java.classpath.9=../lib/spring-beans-3.2.10.RELEASE.jar
wrapper.java.classpath.10=../lib/commons-logging-1.1.1.jar
wrapper.java.classpath.11=../lib/spring-expression-3.2.10.RELEASE.jar
wrapper.java.classpath.12=../lib/spring-retry-1.0.2.RELEASE.jar
wrapper.java.classpath.13=../lib/spring-tx-3.2.10.RELEASE.jar
wrapper.java.classpath.14=../lib/spring-batch-infrastructure-2.2.7.RELEASE.jar
wrapper.java.classpath.15=../lib/spring-aop-3.2.10.RELEASE.jar
wrapper.java.classpath.16=../lib/aopalliance-1.0.jar
wrapper.java.classpath.17=../lib/xstream-1.3.1.jar
wrapper.java.classpath.18=../lib/org.apache.servicemix.bundles.jettison-1.0.1_1.jar

# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=../lib

# Java Bits.  On applicable platforms, tells the JVM to run in 32 or 64-bit mode.
wrapper.java.additional.auto_bits=TRUE

# Java Additional Parameters
wrapper.java.additional.1=

# Initial Java Heap Size (in MB)
wrapper.java.initmemory=100

# Maximum Java Heap Size (in MB)
wrapper.java.maxmemory=500

# Application parameters.  Add parameters as needed starting from 1
#wrapper.app.parameter.1=

#********************************************************************
# Wrapper Logging Properties
#********************************************************************
# Enables Debug output from the Wrapper.
# wrapper.debug=TRUE

# Format of output for the console.  (See docs for formats)
wrapper.console.format=PM

# Log Level for console output.  (See docs for log levels)
wrapper.console.loglevel=INFO

# Log file to use for wrapper output logging.
wrapper.logfile=../logs/wrapper.log

# Format of output for the log file.  (See docs for formats)
wrapper.logfile.format=LPTM

# Log Level for log file output.  (See docs for log levels)
wrapper.logfile.loglevel=INFO

# Maximum size that the log file will be allowed to grow to before
#  the log is rolled. Size is specified in bytes.  The default value
#  of 0, disables log rolling.  May abbreviate with the 'k' (kb) or
#  'm' (mb) suffix.  For example: 10m = 10 megabytes.
wrapper.logfile.maxsize=0

# Maximum number of rolled log files which will be allowed before old
#  files are deleted.  The default value of 0 implies no limit.
wrapper.logfile.maxfiles=0

# Log Level for sys/event log output.  (See docs for log levels)
wrapper.syslog.loglevel=NONE

#********************************************************************
# Wrapper General Properties
#********************************************************************
# Allow for the use of non-contiguous numbered properties
wrapper.ignore_sequence_gaps=TRUE

# Do not start if the pid file already exists.
wrapper.pidfile.strict=TRUE

# Title to use when running as a console
wrapper.console.title=Test Wrapper Sample Application

#********************************************************************
# Wrapper JVM Checks
#********************************************************************
# Detect DeadLocked Threads in the JVM. (Requires Standard Edition)
wrapper.check.deadlock=TRUE
wrapper.check.deadlock.interval=10
wrapper.check.deadlock.action=RESTART
wrapper.check.deadlock.output=FULL

# Out Of Memory detection.
# (Ignore output from dumping the configuration to the console.  This is only needed by the TestWrapper sample application.)
wrapper.filter.trigger.999=wrapper.filter.trigger.*java.lang.OutOfMemoryError
wrapper.filter.allow_wildcards.999=TRUE
wrapper.filter.action.999=NONE
#  Ignore -verbose:class output to avoid false positives.
wrapper.filter.trigger.1000=[Loaded java.lang.OutOfMemoryError
wrapper.filter.action.1000=NONE
# (Simple match)
wrapper.filter.trigger.1001=java.lang.OutOfMemoryError
# (Only match text in stack traces if -XX:+PrintClassHistogram is being used.)
#wrapper.filter.trigger.1001=Exception in thread "*" java.lang.OutOfMemoryError
#wrapper.filter.allow_wildcards.1001=TRUE
wrapper.filter.action.1001=RESTART
wrapper.filter.message.1001=The JVM has run out of memory.

#********************************************************************
# Wrapper Email Notifications. (Requires Professional Edition)
#********************************************************************
# Common Event Email settings.
#wrapper.event.default.email.debug=TRUE
#wrapper.event.default.email.smtp.host=<SMTP_Host>
#wrapper.event.default.email.smtp.port=25
#wrapper.event.default.email.subject=[%WRAPPER_HOSTNAME%:%WRAPPER_NAME%:%WRAPPER_EVENT_NAME%] Event Notification
#wrapper.event.default.email.sender=<Sender email>
#wrapper.event.default.email.recipient=<Recipient email>

# Configure the log attached to event emails.
#wrapper.event.default.email.attach_log=TRUE
#wrapper.event.default.email.maillog.lines=50
#wrapper.event.default.email.maillog.format=LPTM
#wrapper.event.default.email.maillog.loglevel=INFO

# Enable specific event emails.
#wrapper.event.wrapper_start.email=TRUE
#wrapper.event.jvm_prelaunch.email=TRUE
#wrapper.event.jvm_start.email=TRUE
#wrapper.event.jvm_started.email=TRUE
#wrapper.event.jvm_deadlock.email=TRUE
#wrapper.event.jvm_stop.email=TRUE
#wrapper.event.jvm_stopped.email=TRUE
#wrapper.event.jvm_restart.email=TRUE
#wrapper.event.jvm_failed_invocation.email=TRUE
#wrapper.event.jvm_max_failed_invocations.email=TRUE
#wrapper.event.jvm_kill.email=TRUE
#wrapper.event.jvm_killed.email=TRUE
#wrapper.event.jvm_unexpected_exit.email=TRUE
#wrapper.event.wrapper_stop.email=TRUE

# Specify custom mail content
wrapper.event.jvm_restart.email.body=The JVM was restarted.\n\nPlease check on its status.\n

#********************************************************************
# Wrapper Windows NT/2000/XP Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
#  using this configuration file has been installed as a service.
#  Please uninstall the service before modifying this section.  The
#  service can then be reinstalled.

# Name of the service
wrapper.name=mybatch1

# Display name of the service
wrapper.displayname=My Batch1 Wrapper App

# Description of the service
wrapper.description=My Batch1 Wrapper App

# Service dependencies.  Add dependencies as needed starting from 1
wrapper.ntservice.dependency.1=

# Mode in which the service is installed.  AUTO_START, DELAY_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START

# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=false

7) go to /bin folder and run InstallTestWrapper-NT.bat to install the service to window OS







then you can run/stop the service by right-click the new added service.

Or run TestWrapper.bat to test but not as window service

this is the log sample

INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1cb8deef: startup date [Tue Aug 05 12:30:22 SGT 2014]; root of context hierarchy
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loading XML bean definitions from class path resource [app-context.xml]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loading XML bean definitions from class path resource [spring-batch1.xml]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Overriding bean definition for bean 'batch1': replacing [Generic bean: class [org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [org.springframework.batch.core.configuration.xml.JobParserJobFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Overriding bean definition for bean 'batch2': replacing [Generic bean: class [org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [org.springframework.batch.core.configuration.xml.JobParserJobFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@538d7ace: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,dataSource,jobRepository,transactionManager,jobLauncher,org.springframework.batch.core.scope.internalStepScope,org.springframework.beans.factory.config.CustomEditorConfigurer,org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor,step1,batch1,step2,batch2,org.springframework.context.annotation.internalAsyncAnnotationProcessor,org.springframework.context.annotation.internalScheduledAnnotationProcessor,org.springframework.scheduling.support.ScheduledMethodRunnable#0,org.springframework.scheduling.config.CronTask#0,org.springframework.scheduling.support.ScheduledMethodRunnable#1,org.springframework.scheduling.config.CronTask#1,org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar#0,helloTasklet,helloTasklet2,launch1,launch2,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
INFO   | jvm 1    | 2014/08/05 12:30:22 | Aug 5, 2014 12:30:22 PM org.springframework.batch.core.launch.support.SimpleJobLauncher afterPropertiesSet
INFO   | jvm 1    | 2014/08/05 12:30:22 | INFO: No TaskExecutor has been set, defaulting to synchronous executor.
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Job: [FlowJob: [name=batch2]] launched with the following parameters: [{time=1407213024002}]
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Executing step: [step2]
INFO   | jvm 1    | 2014/08/05 12:30:24 | Hello World22222 Tue Aug 05 12:30:24 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:24 | Aug 5, 2014 12:30:24 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:24 | INFO: Job: [FlowJob: [name=batch2]] completed with the following parameters: [{time=1407213024002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213025001}]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:25 | Hello World Tue Aug 05 12:30:25 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:25 | Aug 5, 2014 12:30:25 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:25 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213025001}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213030002}]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:30 | Hello World Tue Aug 05 12:30:30 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:30 | Aug 5, 2014 12:30:30 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:30 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213030002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Job: [FlowJob: [name=batch2]] launched with the following parameters: [{time=1407213032002}]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Executing step: [step2]
INFO   | jvm 1    | 2014/08/05 12:30:32 | Hello World22222 Tue Aug 05 12:30:32 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:32 | Aug 5, 2014 12:30:32 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:32 | INFO: Job: [FlowJob: [name=batch2]] completed with the following parameters: [{time=1407213032002}] and the following status: [COMPLETED]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Job: [FlowJob: [name=batch1]] launched with the following parameters: [{time=1407213035001}]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Executing step: [step1]
INFO   | jvm 1    | 2014/08/05 12:30:35 | Hello World Tue Aug 05 12:30:35 SGT 2014
INFO   | jvm 1    | 2014/08/05 12:30:35 | Aug 5, 2014 12:30:35 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO   | jvm 1    | 2014/08/05 12:30:35 | INFO: Job: [FlowJob: [name=batch1]] completed with the following parameters: [{time=1407213035001}] and the following status: [COMPLETED]
STATUS | wrapper  | 2014/08/05 12:30:35 | <-- Wrapper Stopped

Spring scheduler launches multiple jobs

1. spring configuration file app-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd">

<context:component-scan base-package="wx.batch1" />

<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/testdb2" />
<property name="username" value="tester2" />
<property name="password" value="abcd1234" />
</bean>
       <!-- use mysql to store spring batch meta data -->
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="mysql" />
</bean>

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>

<import resource="classpath:spring-batch1.xml" />
       <!-- configure spring scheduler -->
<task:annotation-driven />
<task:scheduled-tasks>
<task:scheduled ref="launch1" method="runIt" cron="0/5 * * * * *"></task:scheduled>
<task:scheduled ref="launch2" method="runIt" cron="0/8 * * * * *"></task:scheduled>
</task:scheduled-tasks>
</beans>


in order to store sprint batch meta data, need to create tables provided by spring framework, which can be found in spring-batch-core-<version>.RELEASE.jar, they are schema-drop-mysql.sql and schema-mysql.sql

take note, some indexes need to be added in database manually, refer to B.10 at http://docs.spring.io/spring-batch/2.2.x/reference/html/metaDataSchema.html

2) spring batch configuration file spring-batch1.xml

<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="http://www.springframework.org/schema/beans" 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.xsd
           http://www.springframework.org/schema/batch
           http://www.springframework.org/schema/batch/spring-batch-2.2.xsd">

<job id="batch1" job-repository="jobRepository" restartable="true">
<step id="step1">
<tasklet ref="helloTasklet" />
</step>
</job>

<job id="batch2" job-repository="jobRepository" restartable="true">
<step id="step2">
<tasklet ref="helloTasklet2" />
</step>
</job>

</beans:beans>


3) java code to launch job batch1

package wx.batch1;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("launch1")
public class Launch1 {

@Autowired
@Qualifier("jobLauncher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("batch1")
private Job job;

public void runIt() {
JobParameters jobParameters = new JobParametersBuilder().addLong(
"time", System.currentTimeMillis()).toJobParameters();
try {
JobExecution execution = jobLauncher.run(job, jobParameters);
} catch (Exception e) {

e.printStackTrace();
}
}

public JobLauncher getJobLauncher() {
return jobLauncher;
}

public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}

public Job getJob() {
return job;
}

public void setJob(Job job) {
this.job = job;
}

}

4) tasklet for job batch1

package wx.batch1;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Service;

@Service("helloTasklet")
public class HelloWorldTasklet implements Tasklet {

public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println("Hello World " + new java.util.Date());
return RepeatStatus.FINISHED;
}

}


5) java code to launch job batch2

package wx.batch1;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("launch2")
public class Launch2 {

@Autowired
@Qualifier("jobLauncher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("batch2")
private Job job;

public void runIt() {
JobParameters jobParameters = new JobParametersBuilder().addLong(
"time", System.currentTimeMillis()).toJobParameters();
try {
JobExecution execution = jobLauncher.run(job, jobParameters);
} catch (Exception e) {

e.printStackTrace();
}
}

public JobLauncher getJobLauncher() {
return jobLauncher;
}

public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}

public Job getJob() {
return job;
}

public void setJob(Job job) {
this.job = job;
}

}


6) tasklet for batch2

package wx.batch1;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Service;

@Service("helloTasklet2")
public class HelloWorldTasklet2 implements Tasklet {

public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println("Hello World22222 " + new java.util.Date());
return RepeatStatus.FINISHED;
}

}


7) launch class

package wx.batch1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LaunchScheduler {

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

}

Friday, August 1, 2014

JPA + multiple datasources in spring

The target is to configure more than one data source in spring. Need not XA transaction support.

1) datasource1.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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<bean id="txManager1" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf1" />
</bean>

<tx:annotation-driven transaction-manager="txManager1" />

  <bean id="dataSource1"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/testdb1" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>

<bean id="emf1"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db1" />
<property name="dataSource" ref="dataSource1" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="packagesToScan" value="wx.jpa3.entity" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>

</beans>

2) datasource2.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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<bean id="txManager2" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf2" />
</bean>

<tx:annotation-driven transaction-manager="txManager2" />

<bean id="dataSource2"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/testdb2" />
<property name="username" value="tester2" />
<property name="password" value="abcd1234" />
</bean>

<bean id="emf2"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="db2" />
<property name="dataSource" ref="dataSource2" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="packagesToScan" value="wx.jpa3.entity" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>

</beans>

3) spring configure file: app-config.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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<import resource="classpath:datasource1.xml" />
<import resource="classpath:datasource2.xml" />
<context:component-scan base-package="wx.jpa3" />

</beans>

4) dao class using datasource1

package wx.jpa3.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class GenericDaoJpaImpl<T, PK extends Serializable> implements
IGenericDao<T, PK> {

protected Class<T> entityClass;

@PersistenceContext(unitName="db1")
protected EntityManager entityManager;


}

5) dao class using datasource2

package wx.jpa3.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class GenericDaoJpaImpl2<T, PK extends Serializable> implements
IGenericDao2<T, PK> {

protected Class<T> entityClass;

@PersistenceContext(unitName="db2")
protected EntityManager entityManager;

}

6) junit class to try

@RunWith(org.springframework.test.context.junit4.SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:app-context.xml")
@Transactional
@TransactionConfiguration(transactionManager = "txManager2", defaultRollback = false)
public class IStudentServiceTest {

@Autowired
@Qualifier("studentService")
IStudentService studentService;

@Autowired
@Qualifier("studentService2")
IStudentService studentService2;

need to specify which txManager you are going to use.