Java for Web学习笔记(一零二):Spring框架中使用JPA(2)Transaction(下)

上下文的配置

/* 【3】设置事务管理
 * 3.1】允许@Transactional标记有效
 * 使得@Transactional有效,可以被动态截获bean中被标识的方法,对其进行Spring framework method Advice,类似@EnableAsync,需要设置@EnableTransactionManagement。但需要注意:对于方法增强,我们应使用同一方式,如果配置成不同,spring将选择其中的一个,会导致不确定性。因此,@EnableAsync和@EnableTransactionManagement需使用相同的AdviceMode(PROXY or ASPECTJ),以及同样的proxyTargetClass的值,简单地可以分别设置为PROXY和flase。
 * ➤ AdviceMode.PROXY表示通过proxy的类将wrap被标记的方法。动态proxy(proxyTargetClass = false)只在调用标识方法时才对方法进行封装,而CGLIB(proxyTargetClass = true)则会override所有的方法,即调用非标记方法时也会进行方法增强(感觉应该是在创建实例是进行,也因此CGLIB不能用final class)。
 * 1、通常建议proxyTargetClass = false,也就是说这个方法必须是在接口中的,且本类内方法调用时标记是无效的,必须在外部调用才有效。
 * 2、如果我们非要在一个非接口的其他public方法中使用,就需要使用CGLIB proxy,即proxyTargetClass = true,CGLIB proxy的缺点是构造函数被调用了两次。
 * ➤ AdviceMode.ASPECTJ是load-time weaving enabled,也就是说在load的时候,修改编译的bytecode,将相关的方法增强加入,可以用于final类和方法,可以用于类内方法的调用,甚至可以用于非spring bean(适用于传统代码改造)。使用ASPECTJ,还需要加上
* @EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
* 并在pom.xml中加入:
* 
*     org.springframework
*     spring-aspects
*     4.0.2.RELEASE
*     runtime
* 
* 
* 当多个proxy存在的时候,我们还需考虑proxy的顺序。如果transaction proxy在异步proxy之前,可能会导致无法真正异步执行,因此我们设置了合适的顺序。显然异步执行应更优先,否则在@Transactional内调用标记了@Async的方法,异步将不生效。
* */
@Configuration
@EnableScheduling
@EnableAsync(mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.HIGHEST_PRECEDENCE)
@EnableTransactionManagement(mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.LOWEST_PRECEDENCE)
@ComponentScan(basePackages = "cn.wei.flowingflying.chapter21.site",
               excludeFilters = @ComponentScan.Filter({Controller.class, ControllerAdvice.class}) )
public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer{
    private static final Logger log = LogManager.getLogger();
    private static final Logger schedulingLogger = LogManager.getLogger(log.getName() + ".[scheduling]");

   /*【1】设置DataSource。
    * 方式一:如果我们简单地只是使用一个jdbc的连接,这种方式只是在临时项目中使用,可能是测试代码,也可能是原型代码
    * @Bean
    * public DataSource springJpaDataSource(){
    *     DriverManagerDataSource dataSource = new DriverManagerDataSource();
    *     dataSource.setUrl("jdbc:mysql://localhost/SpringJpa");
    *     dataSource.setUsername("tomcatUser");
    *     dataSource.setPassword("password1234");
    *     return dataSource;
    * }
    *
   * 方式二:利用C3P0提供的连接地址池。我们需要在pom.xml中加入
    * 
    *      mysql
    *      mysql-connector-java
    *      5.1.44
    * 
    * 
    *      com.mchange
    *      c3p0
    *      0.9.5.2
    * 
    * 由于我们没有使用tomcat的连接池,因此在war卸装的时候,需要小心地进行关闭。
    * 关闭的代码可以参见http://blog.csdn.net/flowingflying/article/details/51932956。
    * @Bean
    * public DataSource springJpaDataSource() throws PropertyVetoException {
    *     ComboPooledDataSource  dataSource = new ComboPooledDataSource();
    *     dataSource.setDriverClass("com.mysql.jdbc.Driver");
    *     dataSource.setJdbcUrl("jdbc:mysql://localhost/SpringJpa?useSSL=true&useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
    *     dataSource.setUser("tomcatUser");
    *     dataSource.setPassword("password1234");		
    *     dataSource.setMinPoolSize(2);
    *     dataSource.setAcquireIncrement(1);
    *     dataSource.setMaxPoolSize(20);
    *     dataSource.setMaxIdleTime(2);		
    *     dataSource.setInitialPoolSize(2);
    *     dataSource.setIdleConnectionTestPeriod(300);
    *     return dataSource;
    *  }
    * 除了C3P0外,我们还可以使用Apache Commons DBCP,Apache CommonsPool来串接连接池的DataSource。
    *
   * 方式三:延用之前tomcat context配置的方式。即例子给出的方式。
    */
    @Bean
    public DataSource springJpaDataSource(){
        JndiDataSourceLookup lookup = new JndiDataSourceLookup();
        return lookup.getDataSource("jdbc/learnTest");
    }

   /*【2】创建persistence unit。在Spring中,我们需要一个bean来实现org.springframework.orm.jpa.AbstractEntityManagerFactoryBean,它将创建SharedEntityManagerBean来为我们的repository提供线程安全的事务。
    * 方式一:通过LocalEntityManagerFactoryBean实现,使用/META-INF/persistence.xml配置文件,通过设置persistence unit Name来设置
    * @Bean
    * public LocalEntityManagerFactoryBean entityManagerFactoryBean(){
    *     LocalEntityManagerFactoryBean factory = new LocalEntityManagerFactoryBean();
    *     factory.setPersistenceUnitName("SpringJpa");
    *     factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    *     factory.setDataSource(this.springJpaDataSource());
    *     return factory;
    * }

   * 方式二:通过LocalContainerEntityManagerFactoryBean实现,来指定xml配置文件
    * @Bean
    * public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
    *     LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    *     factory.setPersistenceXmlLocation("classpath:com/wrox/config/persistence.xml");
    *     factory.setPersistenceUnitName("SpringJpa");
    *     factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    *     factory.setDataSource(this.springJpaDataSource());
    *     return factory;
    * }
  
   * 方式三:通过LocalContainerEntityManagerFactoryBean实现,在代码中进行配置,不适用xml配置文件。
    * 即小例子给出的代码。在之前学习中,我们通过persistence.xml来进行设置,可以进行对照,实际是相同的。*/
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
        //2.1) 创建map用于存放JPA的properties配置
        Map properties = new Hashtable<>();
        properties.put("javax.persistence.schema-generation.database.action","none");
        //2.2) 创建hibernate adapter作为factory的adaptor,提供了很多信息
        //  ➤ 相当于persistence.xml中的org.hibernate.jpa.HibernatePersistenceProvider
        //  ➤ 告知SharedEntityManagerBean其所需要proxy的EntityManagerFactory(扩展为是HibernateEntityManagerFactory)和EntityManager(扩展为HibernateEntityManager)。
        //  ➤ 告知spring需要翻译Hibernate相关的ORM异常为DataAccessException们。
        //  ➤ 可为Hibernate配置正确的是数据库语言。传统的dialect是MySQL 4.x,为了避免版本上的歧义,最好明确进行设定
        //     - MySQL 4.x Generic : org.hibernate.dialect.MySQLDialect(对MySQL的缺省选择)
        //     - MySQL 4.x MyISAM Engine :org.hibernate.dialect.MySQLMyISAMDialect
        //     - MySQL 4.x InnoDB Engine:org.hibernate.dialect.MySQLInnoDBDialect
        //     - MySQL 5.x+ Generic + MyISAM : org.hibernate.dialect.MySQL5Dialect
        //     - MySQL 5.x+ InnoDB Engine :org.hibernate.dialect.MySQL5InnoDBDialect
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(adapter);
        //2.3) 设置DataSource,相当于xml配置中的和transaction-type为RESOURCE_LOCAL。如果使用setJtaDataSource()则相当于和transaction-type为JTA,这一般用于多数据源的情况。
        factory.setDataSource(this.springJpaDataSource());
        //2.4)设置扫描路径。相当于true,并在中列出entity class
        factory.setPackagesToScan("cn.wei.flowingflying.chapter21.site.entity");
        factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
        factory.setValidationMode(ValidationMode.NONE);
        factory.setJpaPropertyMap(properties);
        return factory;
    }

  /** 3.2】设置PlatformTransactionManager Bean
    * 当我们使用@EnableTransactionManagement,就必须设置PlatformTransactionManager bean。对于JPA来讲,使用JpaTransactionManager,需要在构造函数中关联了EntityManagerFactory。
    * 本例子给出了单一PlatformTransactionManager Bean的代码。如果存在多个PlatformTransactionManager Bean,则jpaTransactionManager是缺省的事务管理器,其例子如下
    *  public SomeService{
    *      @Transactional public void actionOne(); //使用缺省的TransactionManager
    *      @Transactional("jpaTransactionManager") public void actionTwo();
    *      @Transactional("dataSourceTransactionManager") public void actionThree();
    * }
    * 设置其他PlatformTransactionManager的方式如下:
    * 1、设置其他PlatformTransactionManager的例子:
    * @Bean
    * public PlatformTransactionManager dataSourceTransactionManager(){
    *     return new DataSourceTransactionManager(this.springJpaDataSource()); //用于简单的数据源操作。
    * }
    * 2、设置缺省值。对于JTA@Transactional标记,spring使用缺省的PlatformTransactionManager,这是由TransactionManagementConfigurer 返回的,如果由多个事务管理器,则是TransactionManagementConfigurer里面名为txManager,如果我们使用xml作为配置(可以在网上search一下),就比较清晰了。如果我们使用的spring的@Transactional标记,则可以在标记中指定使用哪个PlatformTransactionManager,如果没有指明,则使用缺省的,即事务管理会返回一个(第一个)实现PlatformTransactionManager的名为txManager的bean。但是如果存放多个PlatformTransactionManagers,我们最好明确指明哪个是缺省的,这在spring中的TransactionManagementConfigurer 接口通过annotationDrivenTransactionManager()返回,因此需要继承TransactionManagementConfigurer,即
    * public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer, TransactionManagementConfigurer{
    *     @Override
    *     public PlatformTransactionManager annotationDrivenTransactionManager() {
    *         return this.dataSourceTransactionManager();
    *     }
    * 這里有一个问题:这里不能返回this.jpaTransactionManager()。在entityManagerFactoryBean()中的factory.setDataSource(this.springJpaDataSource())会调用annotationDrivenTransactionManager()方法,如果annotationDrivenTransactionManager()方法调用jpaTransactionManager(),则jpaTransactionManager()就会调用entityManagerFactoryBean,这是个循环,会报错的。
    * 如果只有一个数据源,如本例子所示,我们不需要这样进行操作,但是如果有多种数据源,我们就需要考虑了。*/
    @Bean
    public PlatformTransactionManager jpaTransactionManager(){
        return new JpaTransactionManager(this.entityManagerFactoryBean().getObject());
    }
    .....	
}

相关链接: 我的Professional Java for Web Applications相关文章

你可能感兴趣的:(JAVA,读书笔记,愷风(Wei)之Java,for,Web学习笔记)