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>