[JavaWeb][Spring][使用注解配置Spring][Spring/SpringMVC/SpringDataJPA/MyBatis]

[JavaWeb][Spring][使用注解配置Spring]

前言

Spring是JavaWeb开发人员最熟悉的框架了
在Spring 3.0 之前 Spring只能使用xml配置
因为过于繁杂和冗赘
以至于被称之为xml地狱

Spring官方也注意到了这个问题
于是提供了以Java和注解的方式配置Spring启动

Spring+Spring MVC+Spring Data JPA

首先 引入依赖 这个项目采用Gradle构建

依赖项

apply plugin: 'java'
apply plugin: 'idea'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
//Spring依赖 包括核心包context 事务包tx 服务包webmvc 数据对象包jpa
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' 
compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE' 
compile group: 'org.springframework', name: 'spring-tx', version: '4.3.18.RELEASE' 
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.18.RELEASE' 
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.14.RELEASE'
//aspectj切面支持包
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1' 
 //apache 文件上传包
compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'  
// hibernate核心包
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.3.Final' 
//日志依赖 包括slf4j,log4j2核心包 jcl-slf4j  log4j2-slf4j桥接包
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' 
compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' 
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.11.0' 
//jackson依赖 处理spring的json转换  
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5' 
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5' 
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5' 
//javax依赖 包括servlet xml jstl  spring启动web应用和处理jsp页面依赖这些包
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0' 
compile group: 'javax.servlet', name: 'jstl', version: '1.2' 
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1' 
//druid数据库连接池
compile group: 'com.alibaba', name: 'druid', version: '1.1.10'     
//mysql驱动
runtime group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'  
//junit测试依赖 
testCompile group: 'junit', name: 'junit', version: '4.12'
}

Spring核心库 不需要像之前那样一个个引入
官方提供了 一个一次引入全部核心库的依赖

compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE' 

目录结构

目录结构

java/xyx/my/core/config 目录下用来存放配置类

整合

使用Java配置 那自然要建立配置类

首先是Spring容器自身的启动配置类

Spring 核心

RootConfig.java

package xyz.my.core.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/*
 * RootConfig配置  * Spring容器的配置文件  * 等于配置applicationContext.xml
 * * 需要Spring管理的Bean
 * 在这里注册  */ 
 @Configuration 
 @EnableAspectJAutoProxy // 相当于 xml 中的 <aop:aspectj-autoproxy/> 
 @EnableTransactionManagement // 开启注解式事务管理 
 @ComponentScan(basePackages ={"xyz.my"}, excludeFilters =
        { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = { Controller.class,RestController.class,ControllerAdvice.class}) }) 
 //设置扫描Spring注解的范围 此处不扫描WebMVC的注解 mvc注解交由WebAppConfig配置类处理 
public class RootConfig {
 
}

使用Java配置Spring 主要的工具是注解

@Configuration 标明这是一个Spring配置类 这个标签相当于xml配置的
< context:component-scan/ >
框架在启动的时候会自动扫描 标注有@Configuration注解的类 将其配置数据初始化
@EnableAspectJAutoProxy 启动Spring的切面织入支持 相当于xml中的
< aop:aspectj-autoproxy/>
@EnableTransactionManagement 启动Spring注解式事务支持 类似xml中的
< tx:annotation-driven/>
@ComponentScan 配置启动时Spring要扫描并自动注入的类 以包路径为扫描路径
这里配置了basePackages ={"xyz.my"}以全部包路径为扫描路径 包含了所有代码 但是通过配置了
@ComponentScan.Filter扫描过滤器 排除了@Controller注解标注的类
因为这些类是HTTP请求映射处理类 应由SpringMVC管理 后面会使用SpringMVC的配置类WebAppConfig处理

如果有需要注册在Spring容器应用上下文中的组件
可以使用@Bean注解在RootConfig 中配置

当然也可以使用@Component注解在单独的配置类中注册 两种方式都可以

接下来是SpringMVC的配置类

SpringMVC的配置相对来说比较复杂

因为SpringMVC处理的资源范围要多得多

SpringMVC官方文档支持 SpringMVC包含有以下几个方面的配置

  • 类型转换
  • 合法性校验
  • 拦截器
  • 请求内容体
  • 消息转换
  • 视图控制器
  • 视图解析器
  • 静态资源
  • 默认Servlet
  • 路径匹配

SpringMVC

WebAppConfig.java

package xyz.my.core.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration 
@EnableWebMvc //等效于<mvc:annotation-driven /> 
@ComponentScan(basePackages ={"xyz.my.action","xyz.my.actionHandler"},
        includeFilters = @ComponentScan.Filter
  (type = FilterType.ANNOTATION,
                        value = {Controller.class,RestController.class
                        ,ControllerAdvice.class})) 
public class WebAppConfig extends WebMvcConfigurerAdapter { 

    /**
     * 设置由 web容器处理静态资源
     * 相当于 xml中的<mvc:default-servlet-handler/>  
     */  
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
 {  configurer.enable();
     }   
        
   /**
     * 描述 : <注册静态资源处理器>.
     *<p><使用方法说明></p> 
     */
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
  log.info("addResourceHandlers");

        registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
        registry.addResourceHandler("/libs/**").addResourceLocations("/libs/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/images/**").addResourceLocations("/images/");
        registry.addResourceHandler("/template/**").addResourceLocations("/template/");
        registry.addResourceHandler("/favicon/**").addResourceLocations("/favicon/");
    }    
    
    /**
      * 描述 : <注册响应拦截器>.
      *<p>   <使用方法说明>   </p>  * 
      */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
     log.info("addInterceptors");
 
  }      
  
  
  /**
 * 描述 : <注册JSP视图处理器>.
 *<p>   <使用方法说明>   </p>  * @return
  */
  @Bean
  public ViewResolver JSPviewResolver() {
  log.info("JSPViewResolver");
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/jsp/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setOrder(1);
        return viewResolver;
    }       
    
      /**
        * 描述 : <注册JSON内容数据转换器>  
        *<p>   <使用方法说明>   </p>  
        * @return
        */    
  @Bean
  public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
  RequestMappingHandlerAdapter rma=new RequestMappingHandlerAdapter();
        MappingJackson2HttpMessageConverter Jackson2Converter = new MappingJackson2HttpMessageConverter();

        List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
        supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE));
        supportedMediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
        supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_PLAIN_VALUE));
        Jackson2Converter.setDefaultCharset(Charset.forName("utf-8"));
        Jackson2Converter.setSupportedMediaTypes(supportedMediaTypes);
        List<HttpMessageConverter<?>> lstConverter=new ArrayList<>();
        lstConverter.add(Jackson2Converter);
        rma.setMessageConverters(lstConverter);
        return rma;
    }    
    
    
    /**
     * 描述 : <注册上传限制器>.
     *<p>   <使用方法说明>   </p>  
     * @return
    */
  @Bean(name = "multipartResolver")
  public CommonsMultipartResolver commonsMultipartResolver(ServletContext servletContext) {
  log.info("multipartResolver");
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        commonsMultipartResolver.setServletContext(servletContext);
        commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);//上传文件 byte 最大100M
        return commonsMultipartResolver;
    }
}

WebAppConfig中 @EnableWebMvc 是启用SpringMVC 等效于< mvc:annotation-driven />
它会从 WebMvcConfigurationSupport 导入 Spring MVC 的配置,会在处理请求时加入注解的支持(比如 @RequestMapping@ExceptionHandler等注解

此处也配置了包扫描 扫描路径是action请求分发包的内容和请求异常处理包actionHandler
配置需要扫描的类是Controller.class,ControllerAdvice.class

相比RootConfig,WebAppConfig.java的配置内容就很多了 当然这也是因为HTTP请求处理的复杂性

第一个 启用根Web容器处理静态资源

   /**
     * 设置由 web容器处理静态资源
     * 相当于 xml中的<mvc:default-servlet-handler/>  
     */  
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
 {  
    configurer.enable();
 }   
        

这个配置是为了 将静态资源的处理由SpringMVC的前端控制前DispatchServlet交给默认的Servlet
即Web应用容器(如果使用的是tomcat就是tomcat容器的servlet)处理
因为默认情况下DispatchServlet是会拦截所有的请求路径 即拦截/**
此时就无法对静态资源进行访问 所以必须把静态资源的处理转交给默认的Servlet
使DispatchServlet不再拦截对静态资源的访问 同时也降低了Spring需要处理的请求数量

第二个 注册要处理的静态资源路径

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
  log.info("addResourceHandlers");

        registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
        registry.addResourceHandler("/libs/**").addResourceLocations("/libs/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/images/**").addResourceLocations("/images/");
        registry.addResourceHandler("/template/**").addResourceLocations("/template/");
        registry.addResourceHandler("/favicon/**").addResourceLocations("/favicon/");
    }  

前面说了 静态资源由默认的根Servlet来处理
那么哪些是静态资源? 这就需要在这里注册和定义静态资源的目录
使用ResourceHandlerRegistry 注册静态资源到默认Servlet处理
addResourceHandler()方法定义访问web应用时的路径
addResourceLocations()方法则定义资源存储的路径 一般是classpath:/js/ 也可以简写成 /js/
或者指定定义为绝对存储路径 此时 写为 **file:D:/data/img_files/**的形式

第三个 注册拦截器

    /**
     * 描述 : <注册响应拦截器>.
     *<p>   <使用方法说明>   </p>  * @return
     */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    log.info("addInterceptors");
    registry.addInterceptor(localeChangeInterceptor); 

  }

拦截器是SpringMVC提供的强大的请求处理机制
但如果不需要 你也可以不配置。 Spring也提供了一些拦截器
比如国际化信息拦截器LocaleChangeInterceptor 动态主题拦截器ThemeChangeInterceptor
可以这样配置


        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**");

第三个 注册视图处理器

/**
 * 描述 : <注册JSP视图处理器>.
 *<p>   <使用方法说明>   </p>  
 * @return
  */ 
  @Bean 
  public ViewResolver JSPviewResolver() {
    log.info("JSPViewResolver");
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/views/jsp/");
    viewResolver.setSuffix(".jsp");
    viewResolver.setViewClass(JstlView.class);
    viewResolver.setOrder(1);
    return viewResolver;
}

注册一个JSP视图管理器 这样才能使用JSP页面
也可使用其他模板引擎 如Thymeleaf freemarker等
当然如果前后端分离 可以不注册视图处理器
直接使用REST传递JSON数据

第四个 注册请求内容适配器

@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
  RequestMappingHandlerAdapter rma=new RequestMappingHandlerAdapter();
    MappingJackson2HttpMessageConverter Jackson2Converter = new MappingJackson2HttpMessageConverter();

    List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
    supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE));
    supportedMediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
    supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_PLAIN_VALUE));
    Jackson2Converter.setDefaultCharset(Charset.forName("utf-8"));
    Jackson2Converter.setSupportedMediaTypes(supportedMediaTypes);
    List<HttpMessageConverter<?>> lstConverter=new ArrayList<>();
    lstConverter.add(Jackson2Converter);
    rma.setMessageConverters(lstConverter);
    return rma;
}

现在前后端之间多使用Json交互 所以此处注册了MappingJackson2HttpMessageConverter
这一json请求转换器 并且配置了此转换器支持的媒体类型 包含text/html text/plain application/json
编码字符集为utf8 并将此转换器注册到请求内容适配器RequestMappingHandlerAdapter中

最后一个 CommonsMultipartResolver 很容易 理解
就是配置可上传文件大小的

ServerInitializer
ServerInitializer用来初始化整个web应用
相当于web.xml
必须配置此初始化类 否则Spring应用无法启动

@Slf4j
public class ServerInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
        implements WebApplicationInitializer {

    /**
     * 配置DispatcherServlet 匹配的路径 web过滤路径
     *
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 引入其他模块的根配置文件
     * 此处引入了核心容器的配置文件RootConfig
     * 数据对象框架的配置文件JPAConfig
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        log.info("启动容器配置文件,包括RootConfig,JPAConfig");
        return new Class<?>[]
                {RootConfig.class, JPAConfig.class};
    }

    /**
     * 引入Spring-MVC模块的根配置文件
     *
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        log.info("启动Web容器配置文件,包括WebAppConfig");
        return new Class<?>[]
                {WebAppConfig.class};
    }

     /**
     * 定义过滤器
     * 此处加载了编码字符集过滤器
     * @return
     */
    @Override
    protected Filter[] getServletFilters() {
        log.info("加入过滤器 包含字符集编码过滤器");

        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return new Filter[]{characterEncodingFilter};
    }


}

Spring Data JPA

SpringDataJPA是一个强大的ORM框架 属于SpringData项目的一部分
但SDJ是一个门面框架 底层需要其他实现了JPA标准的ORM框架支持
包括Hibernate,EclipseLink,OpenJPA等

此处我们使用Hibernate

引入依赖

compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.3.Final'

JPA 配置类

JPAConfig.class

@Configuration 
@EnableJpaRepositories(basePackages="xyz.my.dao") @PropertySource(value="classpath:datasource.properties") 
@Slf4j 
public class JPAConfig {    
  @Autowired
  private Environment environment;

  /**
    * 通过数据库连接池配置数据源
    * 具体配置选项可以查看
    * druid连接池的指南
    */
  @Bean
  public DataSource dataSource() {
  DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(environment.getProperty("db.url"));
        dataSource.setUsername(environment.getProperty("db.username"));
        dataSource.setPassword(environment.getProperty("db.password"));
        dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
        dataSource.setTimeBetweenEvictionRunsMillis(
        Integer.valueOf(environment.getProperty("db.timeBetweenEvictionRunsMillis")));
        dataSource.setPoolPreparedStatements(true);
        dataSource.setInitialSize(Integer.valueOf(environment.getProperty("db.initialSize")));
        dataSource.setMinIdle(Integer.valueOf(environment.getProperty("db.minIdle")));
        dataSource.setMaxActive(Integer.valueOf(environment.getProperty("db.maxActive")));
        dataSource.setValidationQuery(environment.getProperty("db.validationQuery"));
        dataSource.setMaxWait(Long.valueOf(environment.getProperty("db.maxWait")));
        dataSource.setTestWhileIdle(Boolean.getBoolean(
        environment.getProperty("db.testWhileIdle")));

        try {
              dataSource.init();
        } catch (SQLException e) {
              log.error("数据库连接池启动异常 {}", e);
              throw new RuntimeException(e);
        }
        return dataSource;
    }    

   /**
    * 启用JPA事务管理器
    */
  @Bean
  public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
    return new JpaTransactionManager(emf);
  }    


   /**
    * 启用JPA数据适配器
    * 并指明数据库类型为MySQL
    * 自动生成表为false 这个属性在后面JPA属性里配置
    */
  @Bean
  public JpaVendorAdapter jpaVendorAdapter() {
  HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setDatabase(Database.MYSQL);
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setShowSql(true);
        return jpaVendorAdapter;
    }    

 /**
    * 配置JPA持久化工厂属性
    * 设置扫描的数据模型包xyz.my.model
    * 配置JPA属性 hibernate.format_sql 格式化SQL语句为true
    * 配置JPA属性 hibernate.hbm2ddl.auto 自动生成表为validate(校验模式)
    */

  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
  LocalContainerEntityManagerFactoryBean lemfb = new LocalContainerEntityManagerFactoryBean();
        lemfb.setDataSource(dataSource());
        lemfb.setJpaVendorAdapter(jpaVendorAdapter());
        lemfb.setPackagesToScan("xyz.my.model");
        Properties jpaProperties = new Properties();
        jpaProperties.setProperty("hibernate.format_sql", "true");
        jpaProperties.setProperty("hibernate.hbm2ddl.auto", "validate");
        lemfb.setJpaProperties(jpaProperties);
        return lemfb;
    }   
 }

@EnableJpaRepositories(basePackages=“xyz.my.dao”)
启用SpringDataJPA的注解 并指定扫描的dao层接口包为xyz.my.dao
@PropertySource(value=“classpath:datasource.properties”)
引入属性配置文件datasource.properties
将数据库配置写在配置文件中 便于后续修改

datasource.properties内容

db.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 
db.username=admin 
db.password=admin 
db.driverClass=com.mysql.jdbc.Driver 
db.initialSize=1 
db.maxActive=8 
db.minIdle=1 
db.testWhileIdle=true 
db.testOnBorrow=false 
db.timeBetweenEvictionRunsMillis=60000 
db.maxWait=60000 
db.validationQuery=SELECT 1

这里的配置基本采用Druid的推荐配置 根据本机条件微调 比如maxActive一般等于或稍大于服务器CPU逻辑核心数

url的配置 为db.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
和普通的MySQL配置大不相同 是因为使用了MySQL8.0
MySQL8.0 做了大量的更新 所以必须在连接字符串中显示指定是否启用安全连接 服务时区

启用切面事务管理
 @Configuration
 @Slf4j 
 public class TxAdviceAspectJ {
  private static final int TX_METHOD_TIMEOUT = 30;
  private static final String AOP_POINTCUT_EXPRESSION = "execution(* xyz.my..service.*.*(..))";

  @Autowired
  private PlatformTransactionManager transactionManager;

    @Bean
  public TransactionInterceptor txAdvice() {
  //按名称匹配事务属性
  NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        /*事务属性规则器 只读事务,不做更新操作*/
  RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        //设置事务传播行为 PROPAGATION_NOT_SUPPORTED不支持事务
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
        /*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
  RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));//设置事务回滚行为 遇异常回滚
           //设置事务传播行为 REQUIRED需要事务:有事务时按事务执行 无事务时创建一个新事务
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setTimeout(TX_METHOD_TIMEOUT);//设置事务超时时间
        Map<String, TransactionAttribute> txMap = new HashMap<>(10);

        /*添加事务自动代理 前缀名匹配*/
 /*增删改 匹配变更事务*/
  txMap.put("add*", requiredTx);
        txMap.put("save*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("edit*", requiredTx);
        txMap.put("update*", requiredTx);
        txMap.put("delete*", requiredTx);

        /*查询 匹配只读事务*/
  txMap.put("get*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        txMap.put("find*", readOnlyTx);
        source.setNameMap( txMap );

        /*以配置内容 创建事务管理拦截器 注入应用上下文*/
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        return txAdvice;
    }    
    
  @Bean
  public Advisor txAdviceAdvisor() {
  AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        log.debug("事务行为处理器切点{}",pointcut.getPointcutExpression());
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    } }

以上的配置是使用切面环绕的方式进行事务管理
网上一般的注解式事务管理都是使用**@Transcational**标记实现
但这种方法太麻烦 难道每个持久化方法都要标记吗?
反而不如xml配置方便了

所以在以上采用Java配置了切面环绕的事务管理

public TransactionInterceptor txAdvice() 切面拦截器等同于xml配置(xml配置为简化代码)

<tx:advice id="txadvice" transaction-manager="transactionManager">

<tx:attributes>
<tx:method name="get*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="query*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="find*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="del*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="list*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>

</tx:advice>

public Advisor txAdviceAdvisor() 注入切点 等同于xml配置

<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(*xyz.my..service.*.*(..))"/>
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txadvice"/>
</aop:config>

日志配置

项目中日志部分显然是必不可少的
在此使用Slf4J+Log4j2

log4j2引入了全新的异步队列日志 比logback要快很多倍
Spring自身使用的是commons-logging 即JCL
我们使用SLF4j作为门面 用它的应用接口编程
底层使用Log4j2输出日志

综合以上 我们需要两大桥接包
jcl-over-slf4j将jcl的输出桥接到slf4j
log4j-slf4j-imp将slf4j的日志信息由log4j输出
另外由于是web项目 还需要log4j-web 依赖

依赖项在本文最初有写

在resource目录下 建立log4j的配置文件

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?> 
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> 
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
 <configuration status="WARN" monitorInterval="30">     
 <!--先定义所有的appender-->    
 <appenders>     
 <!--这个输出控制台的配置-->    
	  <console name="Console" target="SYSTEM_OUT">   
	  <!--输出日志的格式-->
	  <PatternLayout pattern="log4j2:[%d{HH:mm:ss:SSS}] [%p]-%l-%m%n"/>   
	  </console>   
  <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
  <File name="log" fileName="log/test.log" append="false">   
		  <PatternLayout pattern="%d{HH:mm:ss.SSS} 
		  %-5level %class{36} %L %M %msg%xEx%n"/>   
  </File>     
  
  <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
  <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
  filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">   
  <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
  <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>   
	  <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>   
	  <Policies>   
		  <TimeBasedTriggeringPolicy/>   
		  <SizeBasedTriggeringPolicy size="100 MB"/>   
	  </Policies>     
  </RollingFile>     
  <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
  filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">  
   
  <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>   
  <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>   
	  <Policies>   
		  <TimeBasedTriggeringPolicy/>   
		  <SizeBasedTriggeringPolicy size="100 MB"/>   
	  </Policies>   
  <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
  <DefaultRolloverStrategy max="20"/>     
  </RollingFile>     
  <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
  filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">   
  <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>   
  <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>   
		<Policies>  
	    <TimeBasedTriggeringPolicy/>   
	    <SizeBasedTriggeringPolicy size="100 MB"/>   
	    </Policies>   
 </RollingFile>     
 </appenders>     
   
   <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->   
  <loggers>   
    <!--过滤掉spring一些无用的DEBUG信息-->
    <logger name="org.springframework" level="info">
      </logger> 
            <!--root 真正引入需要打印的日志内容-->  
            <root level="all">   
            <appender-ref ref="Console"/>   
            <appender-ref ref="log"/>   
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/> 
            <appender-ref ref="RollingFileError"/> 
            </root>   
      </loggers>    
 </configuration>

到这里 一个基本的Spring Web应用已经配置完毕

业务内容

从持久化层 开始建立

新建一个模型对象 Student

import lombok.Builder;
import lombok.Data;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.sql.Timestamp;

@Entity 
@DynamicUpdate 
@DynamicInsert 
@Table(name = "t_student") 
@Data 
@Builder 
public class Student {    

    @Id
    @Column(name = "id", nullable = false, length = 32)
    @GeneratedValue(generator="UserGenerator")
    @GenericGenerator(name="UserGenerator", strategy="uuid")
    private String id;
    private String name;
    private Character sex;
    private Integer age;
    private String grade;
    private String aliasname;

    private Timestamp intime;
    private Timestamp modtime;
}

模型对象 Student 同时也是持久化对象
直接映射到对应的数据库表
@Entity 表示这是一个持久化对象
@Table(name = “t_ student”) 表示映射到对应的数据库表 t _sudent
@DynamicUpdate @DynamicInsert 表示动态插入
即只插入 变更有变化的成员属性

@Id 表示此成员属性为数据库表的主键
@Column(name = “id”, nullable = false, length = 32)
表示此属性对应的数据库表列名为id 不可为空 长度32位
下面两个注解为生成器注解
@GeneratedValue(generator=“UserGenerator”) 生成器名为UserGenerator
@GenericGenerator(name=“UserGenerator”, strategy=“uuid”)
使用UserGenerator 生成UUID类型的数据
生成器主要用于自动生成主键
这是Hibernate的功能 具体查看Hibernate文档

有了持久化对象 可以建立持久化层了

@Repository
public interface StudentDao extends JpaRepository<Student,String> {

    List<Student> findByName(String name)throws Exception;
}

持久化层的类很简单 添加@Repository 注解并继承JpaRepository接口即可
不用定义持久化对象的增删改查方法 也不用写其内容
因为SpringDataJPA已经替你生成好了

服务层

@Service
public class FirstService {

    @Autowired
    private StudentDao studentDao;

    public Result getStudent(String id)throws Exception {
        Student student=studentDao.findOne(id);
        return Result.init().setData("student",student);
    }

    public Result saveStudent(Student student)throws Exception {
        studentDao.save(student);
        return Result.init();
    }

    public Result updateStudent(Student student)throws Exception {
        Student newStudent=studentDao.findOne(student.getId());
        studentDao.save(newStudent);
        return Result.init();
    }

    public Result deleteStudent(String id)throws Exception {
        Student student=studentDao.findOne(id);
        studentDao.delete(student);
        return Result.init();
    }
}

路由层

@RestController
@Slf4j
@RequestMapping("first")
public class FirstAction {

    @Autowired
    private FirstService firstService;

    @GetMapping("student/{id}")
    public Result GetStudent(@PathVariable("id")String id)throws Exception{
        log.debug("访问get接口 id={}",id);
        return Result.init();
    }

    @PutMapping("student")
    public Result addStudent(@RequestBody Student student)throws Exception{
        log.debug("访问put接口 Student={}",student);
        return Result.init();
    }


    @PostMapping("student")
    public Result updateStudent(@RequestBody Student student)throws Exception{
        log.debug("访问post接口 Student={}",student);
        return Result.init();
    }


    @DeleteMapping("student/{id}")
    public Result deleteStudent(@PathVariable("id")String id)throws Exception{
        log.debug("访问delete接口 id={}",id);
        return Result.init();
    }

    @PutMapping("add")
    public Result increaseStudent()throws Exception{
        log.debug("访问increaseStudent接口");
        return firstService.addStudent();
    }



}

至此一个可用的web应用已经全部搭建完毕

Spring+Spring MVC+MyBatis

依赖项

apply plugin: 'java'
apply plugin: 'idea'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
//Spring依赖 包括核心包context 事务包tx 服务包webmvc 数据对象包jpa
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' 
compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE' 
compile group: 'org.springframework', name: 'spring-tx', version: '4.3.18.RELEASE' 
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.18.RELEASE' 
compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.18.RELEASE'

//MyBatis依赖
compile group: 'org.mybatis', name: 'mybatis', version: '3.4.6'
compile group: 'org.mybatis', name: 'mybatis-spring', version: '1.3.2'

//aspectj切面支持包
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1' 
 //apache 文件上传包
compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'  
//日志依赖 包括slf4j,log4j2核心包 jcl-slf4j  log4j2-slf4j桥接包
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' 
compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' 
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0' 
compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.11.0' 
//jackson依赖 处理spring的json转换  
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5' 
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5' 
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5' 
//javax依赖 包括servlet xml jstl  spring启动web应用和处理jsp页面依赖这些包
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0' 
compile group: 'javax.servlet', name: 'jstl', version: '1.2' 
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1' 
//druid数据库连接池
compile group: 'com.alibaba', name: 'druid', version: '1.1.10'     
//mysql驱动
runtime group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'  
//junit测试依赖 
testCompile group: 'junit', name: 'junit', version: '4.12'
}

整合

Spring SpringMVC的配置和上篇文章的是一样的
只是在整合数据映射框架的配置不一样的

只有Mybatis的配置不一样

MyBatis

要在Spring中使用MyBatis,需要在Spring的配置文件中定义一些类。下面先了解一下这些核心类。

  1. SqlSessionFactoryBean

Sql会话的工厂 由此类创建数据库访问会话SqlSession

在MyBatis中,SqlSessionFactory可以使用SqISessionFactoryBuilder来创建,而在MyBatis-Spring中,使用SqISessinFacotryBean来代替,可以直接在Spring通过配置文件的形式配置。

  1. MapperFactoryBean
    数据对象映射的工厂 由此类创建数据对象到数据查询结果集的映射
    MapperFactoryBean实现了Spring的FactoryBean接口,通过mapperlnterface属性注入接口,将映射接口变成可以注射的Spring Bean。

  2. SqlSessionTemplate
    数据访问会话管理

SqlSessinTemplate类负责管理MyBatis的SqlSession,调用MyBatis的SQL映射语句,轻松实现数据库的访问。SqlSessionTernplate是线程安全的。当调用SQL方法时,包括通过从映射器getMapper()方法返回的映射器的方法,SqlSessionTemplate会保证使用的SqlSession是和当前Spring事务相关联的。此外,它管理会话的生命周期,包括必要的关闭、提交和回滚操作。

MyBatisConfig.class

@Configuration 
@PropertySource(value="classpath:datasource.properties") 
@MapperScan(value="xyz.my.dao") 
@Slf4j 
public class MyBatisConfig {    
  @Autowired
  private Environment environment;

  @Bean
  public DataSource dataSource() {
  DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(environment.getProperty("db.url"));
        dataSource.setUsername(environment.getProperty("db.username"));
        dataSource.setPassword(environment.getProperty("db.password"));
        dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
        dataSource.setTimeBetweenEvictionRunsMillis(Integer.valueOf(environment.getProperty("db.timeBetweenEvictionRunsMillis")));
        dataSource.setPoolPreparedStatements(true);
        dataSource.setInitialSize(Integer.valueOf(environment.getProperty("db.initialSize")));
        dataSource.setMinIdle(Integer.valueOf(environment.getProperty("db.minIdle")));
        dataSource.setMaxActive(Integer.valueOf(environment.getProperty("db.maxActive")));
        dataSource.setValidationQuery(environment.getProperty("db.validationQuery"));
        dataSource.setMaxWait(Long.valueOf(environment.getProperty("db.maxWait")));
        dataSource.setTestWhileIdle(Boolean.getBoolean(environment.getProperty("db.testWhileIdle")));

        try {
  				dataSource.init();
        } catch (SQLException e) {
 				log.error("数据库连接池启动异常 {}", e);
            	throw new RuntimeException(e);
        }
  	return dataSource;
    }      
    
  @Bean
  public DataSourceTransactionManager transactionManager() {
  	return new DataSourceTransactionManager(dataSource());
    }    
    
  @Bean
  public SqlSessionFactory sqlSessionFactory(ApplicationContext applicationContext) 
  throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setTypeAliasesPackage("xyz.my.model");
        Resource[] mapperLocations = applicationContext.getResources("classpath:mapper/**/*.xml");
        sessionFactory.setMapperLocations(mapperLocations);

        return sessionFactory.getObject();
    }   
    
  @Bean
  public org.apache.ibatis.session.Configuration MyBatisConfig
  (org.apache.ibatis.session.Configuration configuration) {    
        //不启用强制 参数命名
        configuration.setUseActualParamName(false);
        //不启用JDBC主键自增策略
        configuration.setUseGeneratedKeys(false);
        //关闭驼峰命名转换:Table{create_time} -> Entity{createTime}
        configuration.setMapUnderscoreToCamelCase(false);
        return configuration;
    }   
    
}

MyBatis的配置比JPA简单一些
首先是数据源Bean 和JPA的配置没有什么区别
然后配置数据源事务管理器DataSourceTransactionManager

第二个配置数据库会话工厂

  @Bean
  public SqlSessionFactory sqlSessionFactory(ApplicationContext applicationContext) 
  throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());//设置数据源
        sessionFactory.setTypeAliasesPackage("xyz.my.model");//设置模型类扫描包目录
        Resource[] mapperLocations = applicationContext.getResources("classpath:mapper/**/*.xml");
        //设置对象映射文件扫描目录
        sessionFactory.setMapperLocations(mapperLocations);

        return sessionFactory.getObject();
    }   

第三个 配置MyBatis属性

  @Bean
  public org.apache.ibatis.session.Configuration MyBatisConfig
  (org.apache.ibatis.session.Configuration configuration) {    
        //不启用强制 参数命名
        configuration.setUseActualParamName(false);
        //不启用JDBC主键自增策略
        configuration.setUseGeneratedKeys(false);
        //关闭驼峰命名转换:Table{create_time} -> Entity{createTime}
        configuration.setMapUnderscoreToCamelCase(false);
        return configuration;
    }  

dao层

@Repository public interface StudentDao {    

    Student get(String id)throws Exception;

    Student add(Student student)throws Exception;

    Student update(Student student)throws Exception;

    Student delete(String id)throws Exception;
}

mapper-xml层

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="xyz.my.dao.StudentDao" >

    <sql id="column"> id,class,name,sex,age,grade,aliasname,intime,modtime </sql>

    <sql id="key_where"> where id=#{id} </sql>

    <!-- 查询 -->
    <select id="get" resultType="xyz.my.model.Student" parameterType="java.lang.String">
        select
        <include refid="column"/>
        from t_student
        <include refid="key_where"/>
    </select>

    <select id="find" resultType="xyz.my.model.Student"
            parameterType="xyz.my.model.Student">
        select
        <include refid="column"/>
        from t_student
        <where>
            <if test="id != null and id != ''">and id=#{id}</if>
            <if test="class != null and class != ''">and class=#{class}</if>
            <if test="name != null and name != ''">and name=#{name}</if>
            <if test="sex != null and sex != ''">and sex=#{sex}</if>
            <if test="age != null and age != ''">and age=#{age}</if>
            <if test="grade != null and grade != ''">and grade=#{grade}</if>
            <if test="aliasname != null and aliasname != ''">and aliasname=#{aliasname}</if>
            <if test="intime != null">and intime=#{intime}</if> 
            <if test="modtime != null">and modtime=#{modtime}</if>
        </where>
        order by intime
    </select>

    <select id="findAll" resultType="xyz.my.model.Student"
            parameterType="xyz.my.model.Student">
        select
        <include refid="column"/>
        from t_student
    </select>

    <!-- 插入 -->
    <insert id="insert" parameterType="xyz.my.model.Student">
        insert into t_student (
        <trim suffixOverrides=",">
            <if test="id != null and id != ''">id,</if>
            <if test="class != null and class != ''">class,</if>
            <if test="name != null and name != ''">name,</if>
            <if test="sex != null and sex != ''">sex,</if>
            <if test="age != null and age != ''">age,</if>
            <if test="grade != null and grade != ''">grade,</if>
            <if test="aliasname != null and aliasname != ''">aliasname,</if>
            <if test="intime != null">intime,</if>
            <if test="modtime != null">modtime</if>
        </trim>
        ) values (
        <trim suffixOverrides=",">
            <if test="id != null and id != ''">#{id},</if>
            <if test="class != null and class != ''">#{class},</if>
            <if test="name != null and name != ''">#{name},</if>
            <if test="sex != null and sex != ''">#{sex},</if>
            <if test="age != null and age != ''">#{age},</if>
            <if test="grade != null and grade != ''">#{age},</if>
            <if test="aliasname != null and aliasname != ''">#{aliasname},</if>
            <if test="intime != null">{intime},</if>
            <if test="modtime != null">#{modtime}</if>
        </trim>
        )
    </insert>

    <!-- 更新 -->
    <update id="update" parameterType="xyz.my.model.Student">
        update t_student
        <trim prefix=" set " suffixOverrides=",">
            <if test="id != null and id != ''">id=#{id},</if>
            <if test="class != null and class != ''">class=#{class},</if>
            <if test="name != null and name != ''">name=#{name},</if>
            <if test="sex != null and sex != ''">sex=#{sex},</if>
            <if test="age != null and age != ''">age=#{age},</if>
            <if test="grade != null and grade != ''">and grade=#{grade}</if>
            <if test="aliasname != null and aliasname != ''">and aliasname=#{aliasname}</if>
            <if test="intime != null">and intime=#{intime}</if>
            <if test="modtime != null">and modtime=#{modtime}</if>
        </trim>
        <include refid="key_where"/>
    </update>

    <!-- 删除 -->
    <delete id="delete" parameterType="xyz.my.model.Student">
        delete from t_student
        <include refid="key_where"/>
    </delete>
</mapper>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值