普通Web项目架构渐进式实现--SSM

普通Web项目架构渐进式实现

    • SpringMVC、Mybatis独立架构实现
      • SpringMVC
        • 依赖库引入和资源文件加入生成路径
        • web.xml配置
        • SpringMVC上下文配置文件
        • Controller类
        • 视图页面
        • 测试
      • Mybatis基础、实现CRUD
        • mybatis配置文件
        • 数据源配置文件
        • Entity类
        • mapper配置文件
        • Dao接口定义
        • 测试
    • Spring MVC做Restful API
    • 使用C3P0数据库连接池
        • 单独的C3P0使用
        • 在mybatis-config.xml配置文件将DataSource改为c3p0之后
    • 集成Ehcache
      • 加入依赖库
        • ehcache配置文件
        • 搭配配置文件使用
        • 无配置文件使用缓存
        • Ehcache工具类
        • Ehcache使用示例
    • Spring集成各组件
      • Spring集成Mybatis
        • Spring集成SqlSessionFactory
        • 清空mybatis-config.xml配置
        • application-context.xml配置
        • 测试托管后的SqlSessionFactory
        • Spring代理Mybatis事务
        • Spring代理Mybatis全注解开发
      • Spring Cache代理Ehcache3
        • 关于Spring Cache - Ehcache3的一些说明
        • 先加入依赖:
        • Spring-Ehcache3缓存配置文件
        • 码代码
        • 缓存测试
      • Apache POI的使用
        • 依赖引入
        • 码代码:读写Excel
      • Excel的上传及下载
        • 配置Spring MVC的上传文件解析器
        • Service代码
        • Cache设置
        • Controller代码
        • 视图页
        • 测试

当下微服务大行其道,以SpringBoot为核心的软件架构逐渐成为各类型后台架构的主体,但是这不意味着SSH、SSM式的架构马上就要被淘汰。相反,我认为微服务架构和传统Web架构应用场景很多并不冲突,在现在可见的几年后SSM这式架构依然会大行其道。趁着我现在有参入到SSM的项目,正好把之前的记录梳理一遍。这篇文章是渐进式的。首先我会给出一个SpringMVC、Mybatis的Demo,然后我会逐渐加入各类组件进去,数据库连接池,二级缓存,Spring代理SqlSession事务、Thymeleaf模板引擎等。最后时间允许的话就加入POI整合成一个报表程序。

SpringMVC、Mybatis独立架构实现

工具:

  1. JDK1.8
  2. Idea
  3. Maven

此项目的Git仓库

更新日志

架构更新日志

  • logback
  • jwt
  • 动态数据源
  • quartz

项目目录
普通Web项目架构渐进式实现--SSM_第1张图片

SpringMVC

依赖库引入和资源文件加入生成路径


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>cn.examplegroupId>
    <artifactId>newWebAppartifactId>
    <version>1.0-SNAPSHOTversion>
    <build>
        <sourceDirectory>javasourceDirectory>
        
        <resources>
            <resource>
                <directory>javadirectory>
                <excludes>
                    <exclude>**/*.javaexclude>
                excludes>
            resource>
        resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>8source>
                    <target>8target>
                configuration>
            plugin>
        plugins>
    build>

    <packaging>warpackaging>

    <dependencies>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webartifactId>
            <version>5.1.8.RELEASEversion>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>5.1.8.RELEASEversion>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.47version>
        dependency>

        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.2version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.47version>
        dependency>

        <dependency>
            <groupId>org.junit.jupitergroupId>
            <artifactId>junit-jupiter-apiartifactId>
            <version>RELEASEversion>
            <scope>testscope>
        dependency>
    dependencies>
project>

web.xml配置

    <servlet>
        <servlet-name>spring-web-mvcservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:applicationContext-MVC.xmlparam-value>
        init-param>
    servlet>
    <servlet-mapping>
        <servlet-name>spring-web-mvcservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
        
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:applicationContext.xmlparam-value>
    context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>

SpringMVC上下文配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/aop
      	http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/Views/">property>
        <property name="suffix" value=".jsp">property>
    bean>

    <context:component-scan base-package="cn.Controller" />
    beans>

Controller类

@Controller
public class IndexController {
     
//	返回视图和Model
    @RequestMapping(value = "/"  , method = RequestMethod.GET)
    public ModelAndView proc1(){
     
        ModelAndView modelAndView = new ModelAndView("home");
        return modelAndView;
    }
    //	Restful API
    @ResponseBody
    @RequestMapping("/hello")
    public String proc2(){
     
        return "good eveing";
    }
    
    @ResponseBody
    @RequestMapping(value = "/oneParam")
    public String oneParamProc( String smsVerifyCode ){
     
        JSONObject result = new JSONObject();
        result.put("status", "Param : " + smsVerifyCode);
        return result.toJSONString();
    }
    
    @ResponseBody
    @RequestMapping(value = "/register")
    public String registerProc( String smsVerifyCode , HttpServletRequest request){
     
        JSONObject result = new JSONObject();
        result.put("state", "success");
        result.put("info", smsVerifyCode );
        return result.toJSONString();
    }
}

视图页面

Views/home.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首頁title>
head>
<body>
<h1>一個普通的是視圖葉!h1>
<p>Hello everyonep>
<hr>
<a href="hello">say helloa>
<hr>
<a href="oneParam?smsVerifyCode=012345">帶一個參數的請求Actiona>
<hr>
<a href="register?smsVerifyCode=123456">也是帶一個參數的Action?a>
body>
html>

测试

普通Web项目架构渐进式实现--SSM_第2张图片


Mybatis基础、实现CRUD

demo使用的数据库名:exampledb,表:employee

+--------+-------------+------+-----+---------+----------------+
| Field  | Type        | Null | Key | Default | Extra          |
+--------+-------------+------+-----+---------+----------------+
| id     | int(11)     | NO   | PRI | NULL    | auto_increment |
| name   | varchar(16) | YES  |     | NULL    |                |
| age    | int(11)     | YES  |     | NULL    |                |
| salary | float       | YES  |     | NULL    |                |
+--------+-------------+------+-----+---------+----------------+

mybatis配置文件

mybatis-config.xml



<configuration>
    <properties resource="database.properties">properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            dataSource>
        environment>
    environments>
    
<mappers>
    <mapper class="cn.Dao.EmployeeDao">mapper>
mappers>

configuration>

数据源配置文件

database.properties

#mysql database setting
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/exampledb
jdbc.username=root
jdbc.password=123456

Entity类

public class Employee implements Serializable {
     
    private Integer id;
    private String name;
    private Integer age;
    private Float salary;

    @Override
    public String toString() {
     
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    public Integer getId() {
     
        return id;
    }

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

    public String getName() {
     
        return name;
    }

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

    public Integer getAge() {
     
        return age;
    }

    public void setAge(Integer age) {
     
        this.age = age;
    }

    public Float getSalary() {
     
        return salary;
    }

    public void setSalary(Float salary) {
     
        this.salary = salary;
    }

    public Employee(Integer id, String name, Integer age, Float salary) {
     
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee() {
     
    }
}

mapper配置文件

在这里插入图片描述




<mapper namespace="cn.Dao.EmployeeDao">

    <select id="selectOne"
            resultType="cn.Entity.Employee">
        select id,name,age,salary from employee where id = #{id}
    select>

    <select id="findAll"
            resultType="cn.Entity.Employee">
        select id,name,age,salary
        from employee
        order by name desc
    select>

    <insert id="addOne" parameterType="cn.Entity.Employee">
        insert into employee(name,age,salary) values(#{name},#{age},#{salary})
    insert>

    <update id="updateOneSalary">
        update employee set salary = #{salary} where id = #{id}
    update>

    <delete id="deleteOne" parameterType="int">
        delete from employee where id = #{id}
    delete>

    <select id="listSalaryLessThanFiveThousandAndAgeLargeThenAge" resultType="cn.Entity.Employee">
        select id,name,age,salary
        from employee
        where salary < 5000
        <if test="null != #{age} ">
            and age > #{age}
        if>
    select>
mapper>

Dao接口定义

接口中的方法签名要与mapper文件中定义的一致,注意观察。

public interface EmployeeDao{
     

    public Employee selectOne(Integer id);
    public List<Employee> findAll();

    public void addOne(Employee employee);

    public void updateOneSalary(@Param("salary") Float salary, @Param("id") Integer id);

    public void deleteOne(Integer id);

    public List<Employee> listSalaryLessThanFiveThousandAndAgeLargeThenAge(Integer age);
}

测试

    @Test
    public void testProc() throws IOException {
     

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(inputStream);

        SqlSession sqlSession = sessionFactory.openSession();
        EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);

        System.out.println("=========================================");
        employeeDao.findAll().forEach(i->{
     
            System.out.println(i.toString());
        });
        employeeDao.addOne(new Employee(null,"張三",46, 4500F));
        employeeDao.addOne(new Employee(null,"李四",55, 8000F));
        sqlSession.commit();
        System.out.println("=========================================");
        employeeDao.findAll().forEach(i->{
     
            System.out.println(i.toString());
        });

        System.out.println("=========================================");
        System.out.println("Identiry is : 1");
        Employee employee = employeeDao.selectOne(1);
        System.out.println(employee.toString());
        employeeDao.updateOneSalary(7500F,1);
        employeeDao.deleteOne(3);
        sqlSession.commit();

        System.out.println("=========================================");
        employeeDao.findAll().forEach(i->{
     
            System.out.println(i.toString());
        });

        System.out.println("=========================================");
        employeeDao.listSalaryLessThanFiveThousandAndAgeLargeThenAge(45).forEach(i->{
     
            System.out.println(i.toString());
        });

        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }
}

普通Web项目架构渐进式实现--SSM_第3张图片

Spring MVC做Restful API

SpringMVC返回JSON或者其它类对象需要做些配置。

加入依赖:

        
        
            com.fasterxml.jackson.core
            jackson-databind
            2.9.9.2
        

修改SpringMVC的上下文配置文件:


    

这样就可以正常转换对象返回输出了。

使用C3P0数据库连接池

加入依赖:


        
        
            com.mchange
            c3p0
            0.9.5.4
        

需要写一个数据源工厂类的子类:

public class MybatisC3p0DatabaseSourcesFactory extends UnpooledDataSourceFactory {
    public MybatisC3p0DatabaseSourcesFactory(){
        this.dataSource = new ComboPooledDataSource();
    }
}

如果是Mybatis使用配置文件几个参数需要修改:

    
    
        
            
            
                
                
                
                
                
                
                
                
            
        
    

单独的C3P0使用

单独的C3P0测试

        MybatisC3p0DatabaseSourcesFactory mybatisC3p0DatabaseSourcesFactory = new MybatisC3p0DatabaseSourcesFactory();
        Properties properties = new Properties();
        properties.setProperty("jdbcUrl", "jdbc:mysql://127.0.0.1:3306/exampledb");
        properties.setProperty("driverClass", "com.mysql.jdbc.Driver");
        properties.setProperty("initialPoolSize", "8");
        properties.setProperty("minPoolSize", "8");
        properties.setProperty("maxPoolSize", "32");
        properties.setProperty("user", "root");
        properties.setProperty("password", "123456");
        mybatisC3p0DatabaseSourcesFactory.setProperties(properties);
        DataSource dataSource = mybatisC3p0DatabaseSourcesFactory.getDataSource();
        try(Connection connection = dataSource.getConnection()){
            String nativeSql = connection.nativeSQL("insert into employee(name,age,salary) values('麦克T',34,1500)");
            System.out.println(nativeSql);
            Statement statement = connection.createStatement();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into employee(name,age,salary) values(?,?,?)");
            for (int i = 0; i < 15000; i++) {
//                statement.execute("insert into employee(name,age,salary) values('麦克',i%60,i*3500%8000)");
                preparedStatement.setString(1,"麦克"+i);
                preparedStatement.setInt(2,i%60);
                preparedStatement.setFloat(3,i*3500%8000);
                preparedStatement.execute();
            }
//            connection.commit();
        }

在mybatis-config.xml配置文件将DataSource改为c3p0之后

此时mybatis的数据源已经是由c3p0管理了。可以在启动时看到相关日志。

其实mybatis本身也有连接池。

集成Ehcache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。Ehcache最初是由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然是开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用,例如Enterprise EHCache and BigMemory。维基媒体Foundationannounced目前使用的就是Ehcache技术。主要的特性有:1. 快速 2. 简单 3. 多种缓存策略 4. 缓存数据有两级/三级:内存(堆、堆外)和磁盘,因此无需担心容量问题 5. 缓存数据会在虚拟机重启的过程中写入磁盘 6. 可以通过RMI、可插入API等方式进行分布式缓存 7. 具有缓存和缓存管理器的侦听接口 8. 支持多缓存管理器实例,以及一个实例的多个缓存区域 9. 提供Hibernate的缓存实现

普通Web项目架构渐进式实现--SSM_第4张图片

加入依赖库

ehcache需要slf4j-api的库。

        
        
            org.ehcache
            ehcache
            3.8.0
        
        
        
            org.mybatis.caches
            mybatis-ehcache
            1.1.0
        
        
        
            org.slf4j
            slf4j-api
            1.7.26
        

ehcache配置文件

ehcache可以使用配置文件创建缓存,也可以摆脱xml配置文件使用代码创建。
ehcache.xml



    

    
        java.lang.Integer
        cn.Entity.Employee

        
            35
        
        

            64

            1

            2
        
    

    
        java.lang.String
        java.lang.String
        
            20
            10
        
    


    
        java.lang.Long
        java.lang.String
        200
    

    
        java.lang.Number
    

    


搭配配置文件使用

        Configuration configuration = new XmlConfiguration(getClass().getResource("ehcache.xml"));
        CacheManager cacheManager = CacheManagerBuilder.newCacheManager(configuration);
        cacheManager.init();

        Cache cache = cacheManager.getCache("myCache1",  String.class, String.class);
        cache.put("001","zhangsan");
        cache.put("002","lisi");
        cache.put("003","wanger");
//        cache.clear();
        cacheManager.close();

无配置文件使用缓存

        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache("preConfigured",
                        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)))
                .build();

        cacheManager.init();

        Cache preConfigured =
                cacheManager.getCache("preConfigured", Long.class, String.class);

        Cache myCache = cacheManager.createCache("myCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));

        myCache.put(1L, "da one!");
        String value = myCache.get(1L);

//        cacheManager.removeCache("preConfigured");

        cacheManager.close();
    }

Ehcache工具类

使用Ehcache一般写个单例工具类方便后期使用。

//  双锁单例ehcache工厂
public class EhcacheFactory {

    private static volatile CacheManager cacheManager;
    private EhcacheFactory(){}

    public static synchronized CacheManager getCacheManager(){
        if(cacheManager!=null){
            return cacheManager;
        }else{
            ProtectedInstance protectedInstance = new ProtectedInstance();
            cacheManager = protectedInstance.getCachaManager();
        }
        return cacheManager;
    }

    public static synchronized CacheManager getCacheManager(boolean hasAvailable){
        if(hasAvailable){
        CacheManager cm = getCacheManager();
            if (cm != null && cm.getStatus() != Status.AVAILABLE) {
                cm.init();
            }
        }
        return cacheManager;
    }
}

class ProtectedInstance{
    public CacheManager getCachaManager(){
        Configuration configuration = new XmlConfiguration(this.getClass().getResource("/ehcache.xml"));
        CacheManager cacheManager = CacheManagerBuilder.newCacheManager(configuration);
        return cacheManager;
    }

    public ProtectedInstance() {
    }
}

Ehcache使用示例

一般缓存查询数据都是放到一个过程里:请求数据,到缓存查,没找到再到数据库里查,在数据库查找的同时放到缓存里,之后都在缓存里了。
为了表述清晰我把这个步骤拆成了两个,方便新手理解。

不要忘记配置SpringMVC的Json转换器哦,不然返回对象时会报类型转换错误的。

查询数据放入缓存

    @ResponseBody
    @RequestMapping("/listAllEmployee")
    public List listAllEmployeeProc() throws IOException {
        List result = new LinkedList<>();

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(inputStream);

        try(SqlSession sqlSession = sessionFactory.openSession()){
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            result = employeeDao.findAll();

            CacheManager cacheManager = EhcacheFactory.getCacheManager();
            if(cacheManager.getStatus()!=Status.AVAILABLE){
                cacheManager.init();
            }

            Cache cache = cacheManager.getCache("employeeCache", Integer.class, Employee.class);

            result.forEach(i->{
                System.out.println(i.toString());
                if(cache!=null)
                    cache.put(i.getId(),i);
            });
        }
      return result;
    }

在缓存里调取数据

    @ResponseBody
    @RequestMapping("/listAllInCache")
    public List getem(){
        List result = new LinkedList<>();
        CacheManager cacheManager = EhcacheFactory.getCacheManager();
        if(cacheManager.getStatus()!=Status.AVAILABLE){
            cacheManager.init();
        }

        Cache cache = cacheManager.getCache("employeeCache", Integer.class, Employee.class);
        if(cache!=null)
        cache.forEach(i->{
            result.add(i.getValue());
        });
//        cache.clear();
        return result;
    }

Okay , 到现在项目的各个框架都搭建完毕。
Spring
SpringMVC
Mybatis
C3P0
Ehcache

下面可以开始将各个组件集成Spring,另外做些拓展。


Spring集成各组件

Spring集成Mybatis

单独使用mybatis是有很多限制的(比如无法实现跨越多个session的事务),而且很多业务系统本来就是使用spring来管理的事务,因此mybatis最好与spring集成起来使用。

Spring集成SqlSessionFactory

首先是加入相关依赖


        
        
            org.mybatis
            mybatis-spring
            2.0.2
        
        
        
            org.springframework
            spring-jdbc
            5.1.9.RELEASE
        

清空mybatis-config.xml配置






application-context.xml配置

    
    
    
    

    
    

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

    
    
        
        
        

        
        
        
        
    
   
    
        
    

测试托管后的SqlSessionFactory

这一步用来测试SqlSessionFactory是否被正确托管

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        SqlSessionFactory sessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");

        EmployeeDao employeeDaoTx = sessionFactory.openSession().getMapper(EmployeeDao.class);

        Employee employee = employeeDaoTx.selectOne(1500);
        System.out.println(employee.toString());

如下正确查到数据就是已经OK了。
在这里插入图片描述

Spring代理Mybatis事务

声明式事务管理

application-context.xml配置

		
       xmlns:tx="http://www.springframework.org/schema/tx"
       
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd

    
    
    
        
    
    
    

    
    
    
    

应用了事务的业务类

public class EmployeeService {
    private EmployeeDao employeeDao;

    public EmployeeService(EmployeeDao ed){
        this.employeeDao = ed;
    }
    public EmployeeService(){}

    public void setEmployeeDao(EmployeeDao ed){
        this.employeeDao = ed;
    }

    @Transactional
    public void batchDeleteEmployee(int begin , int end){
        for (int i = begin; i < end; i++) {
            Employee temp = this.employeeDao.selectOne(i);
            if(temp==null){
                throw new IllegalStateException("没有此员工!");
            }
            this.employeeDao.deleteOne(i);
        }
    }

    public Employee getOne(Integer id){
        return this.employeeDao.selectOne(id);
    }
}

测试

    @ResponseBody
    @RequestMapping("/testTx2")
    public void testTxProc(Integer begin , Integer end){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        SqlSessionFactory sessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
        EmployeeService employeeService = (EmployeeService) applicationContext.getBean("employeeService");

        EmployeeDao employeeDaoTx = sessionFactory.openSession().getMapper(EmployeeDao.class);
        employeeService.setEmployeeDao(employeeDaoTx);
        try {
            employeeService.batchDeleteEmployee(begin, end);
        }catch (IllegalStateException e){
            System.out.println(e.getMessage());
        }finally {
            System.out.println("finally");
        }
    }

普通Web项目架构渐进式实现--SSM_第5张图片
删除51–52
普通Web项目架构渐进式实现--SSM_第6张图片
删除53–60
普通Web项目架构渐进式实现--SSM_第7张图片
因为表里没有第55条记录,抛出了IllegalStateException异常,事务回滚。
事务生效,测试成功。

到现在为止,我们已经用Spring代理了由C3P0做连接池的数据源、SqlSessionFactory,还加了声明事事务管理。但是整体来看只有声明式的事务管理给我们以后的开发减少了代码量,反观获得数据库会话这块并没有给我带来明显的方便,那么我们现在要开始着手做SqlSession的完全代理了。

Spring代理Mybatis全注解开发

一个组件被Spring代理,简要来说分为三个步骤:
将组件放入Spring容器,定义Bean
装载Bean
使用Bean

这个步骤有两种实现:1、在上下文配置文件手写Bean定义。2、使用注解。
我们在上面用的就是手写Bean定义,这种方式太繁琐,而且这个定义Bean的各个步骤又是重复的,如此一来,随着项目业务增多,代码、配置会搞成一团乱麻。注解就是简化这个流程的解决方案,你只需要在你要放入Spring容器的类、接口加上指定的注解就行了。
这是Spring托管Bean的四类注解。
@Controller
@Repository
@Service
@Component

下面我们将项目Mybatis这块改造为注解模式。

Dao接口

@Repository
public interface EmployeeDao

Service实现类

@Service
public class EmployeeServiceImpl implements IEmployeeService 

application-Context.xml配置文件


    

    
    

Controller类载入Service的Bean
不用手动装载Bean了。

    @Autowired
    private IEmployeeService employeeService;

    @ResponseBody
    @RequestMapping("/testTx3")
    public List listSomeEmployee3(Integer begin , Integer count){
        Employee employee = employeeService.getOne(begin);
        List employees = new LinkedList<>();
        employees.add(employee);
        return employees;
    }

在这里插入图片描述
好了,至此我们已经完成了Mybatis的完全代理。


Spring Cache代理Ehcache3

关于Spring Cache - Ehcache3的一些说明

Ehcache3使用还是比较简单的,通过自己写的缓存工具类使用起来也很方便,但是在很多场景下,业务代码交叉很多缓存代码会使得代码的可读性变差和整体复杂度增高,为了使我们的代码更简洁,我们来使用Spring Cache来代理Ehcache3实现注解使用缓存。

这篇文章没有Mybatis集成Ehcache,可能有人会觉得这是个纰漏,实际上Spring Cache可以在任何类和方法上使用缓存机制,而不仅仅是在MyBatis上使用,所以就没必要单独讲Mybatis集成Ehcache,做重复劳动了。

首先是对Spring Cache的缓存机制做个说明;Spring Cache的注解缓存是声明在函数和类上的(此函数的类必须是Spring容器里的Bean)通过AOP机制,对传入的参数和结果做记录,就是说如果你第一次使用这个函数的时候传入的参数是“001”,运行函数内代码、查询数据库、返回结果是“abc”。第二次你再传入"001"实参,不再运行此函数,直接从缓存中返回“abc”。这就是它的基本机制,挺简单的。

有几点需要大家关注下:

Spring暂时还没有直接兼容Ehcache3,只有通过JCache来代理Ehcache3。

内部调用,非public方法上使用注解,会导致缓存无效。由于SpringCache是基于Spring AOP的动态代理实现,由于代理本身的问题,当同一个类中调用另一个方法,会导致另一个方法的缓存不能使用,这个在编码上需要注意,避免在同一个类中这样调用。如果非要这样做,可以通过再次代理调用,如((Category)AopContext.currentProxy()).get(category)这样避免缓存无效

不能支持多级缓存设置,如默认到本地缓存取数据,本地缓存没有则去远端缓存取数据,然后远程缓存取回来数据再存到本地缓存。

缓存的同步 sync:
在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。
例:

@Cacheable(cacheNames="foos", sync="true")
public Foo executeExpensiveOperation(String id) {...}

常用注解
@Cacheable 应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中。
@CacheConfig 主要用于配置该类中会用到的一些共用的缓存配置。
@CachePut 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存。
@CacheEvict 应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据。
@Caching 组合多个Cache注解使用。

缓存策略
如果缓存满了,从缓存中移除数据的策略,常见的有FIFO, LRU 、LFU

FIFO (First in First Out) 先进先出策略,即先放入缓存的数据先被移除
LRU (Least Recently Used) 最久未使用策略, 即使用时间距离现在最久的那个数据被移除
LFU (Least Frequently Used) 最少使用策略,即一定时间内使用次数(频率)最少的那个数据被移除
TTL(Time To Live)存活期,即从缓存中创建时间点开始至到期的一个时间段(不管在这个时间段内有没被访问过都将过期)
TTI (Time To Idle)空闲期,即一个数据多久没有被访问就从缓存中移除的时间。

先加入依赖:


        
        
            javax.cache
            cache-api
            1.1.1
        


        
            org.springframework
            spring-test
            5.1.5.RELEASE
            test
        

        
            junit
            junit
            4.13-beta-3
            test
        

Ehcache3的配置文件还维持之前的不改动。

Spring-Ehcache3缓存配置文件




    Ehcache缓存配置文件

    
    
         
    
    
        
    

    


不要忘了在applicationContext.xml里倒入cache上下文配置文件


码代码

找一个方法加上缓存

    @Override
    @Cacheable(value = "employeeCache" , key = "#id")
    public Employee getOne(Integer id){
        System.out.println("Access Database ... : employeeId:"+id);
        return this.employeeDao.selectOne(id);
    }

缓存测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext.xml","/applicationContext-MVC.xml","/applicationContext-Cache.xml"})
public class TestClass2 {
    @Autowired
    private IEmployeeService employeeService;
    @Test
    public void testProc() throws InterruptedException {

       Employee employee = employeeService.getOne(3522);
        System.out.println(employee.toString());
        Thread.sleep(500);
        employee = employeeService.getOne(3522);
        System.out.println(employee.toString());
    }
}

结果
普通Web项目架构渐进式实现--SSM_第8张图片
现在再运行一次测试,看看缓存的持久化机制启用了没。
普通Web项目架构渐进式实现--SSM_第9张图片


Apache POI的使用

Apache POI 是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API。用它可以使用Java读取和创建,修改MS Excel文件.而且,还可以使用Java读取和创建MS Word和MSPowerPoint文件。Apache POI 提供Java操作Excel解决方案

依赖引入


        
        
            org.apache.poi
            poi-ooxml
            4.1.0
        


        
        
            commons-fileupload
            commons-fileupload
            1.4
        

码代码:读写Excel

    /**
     * 写xls文件
     * @throws IOException
     */
    @Test
    public void testProc2() throws IOException {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet("Sheet1");
        HSSFRow row = sheet.createRow(0);
        HSSFCell cell = row.createCell(0);
        HSSFCell cell2 = row.createCell(1);
        cell.setCellValue("今天是:");
        cell2.setCellValue(new Date().toLocaleString());

        FileOutputStream fileOutputStream = new FileOutputStream("D:/tmp/newTable.xls");

        workbook.write(fileOutputStream);
        fileOutputStream.flush();
        fileOutputStream.close();
        workbook.close();
    }

    /**
     * 写xlsx文件
     * @throws IOException
     */
    @Test
    public void testProc3() throws IOException {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("Sheet1");
        XSSFRow row = sheet.createRow(0);
        XSSFCell cell = row.createCell(0);
        XSSFCell cell2 = row.createCell(1);
        cell.setCellValue("Hello");
        cell2.setCellValue("Apache POI");

        FileOutputStream fileOutputStream = new FileOutputStream("D:/tmp/newTable2007.xlsx");

        workbook.write(fileOutputStream);
        fileOutputStream.flush();
        fileOutputStream.close();
        workbook.close();
    }

    /**
     * 读xlsx文件
     * @throws IOException
     */
    @Test
    public void testProc4() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("D:/tmp/newTable2007.xlsx");

        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(fileInputStream);
        XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
        System.out.println("读取Excel文件:"+"newTable2007.xlsx");
        System.out.println("读物Sheet:"+sheet.getSheetName());

        sheet.forEach(i->{
            i.forEach(ii->{
                System.out.print(ii.getStringCellValue()+" ");
            });
            System.out.println();
        });

        xssfWorkbook.close();
        fileInputStream.close();
    }

Excel的上传及下载

为了让大家更好的了解Spring Cache和POI的作用,我把它俩整到一块做个例子。

配置Spring MVC的上传文件解析器

Spring MVC上下文配置文件:

    
    
        
            1024000
        
        
    

Service代码

要注意@Cacheable注解应用到无参函数上的key设置
spEL的#root对象。

    @Override
    @Cacheable(value = "allEmployee" , key = "#root.methodName")
    public List findAll() {
        System.out.println("Access Database ..." + new Date().toString());
        return this.employeeDao.findAll();
    }

Cache设置

    
        java.lang.String
        java.util.List
        
        
            35
        
        
            
            64
            
            1
            
            2
        
    

Controller代码

@Controller
public class PoiController {
    @Autowired
    private IEmployeeService employeeService;
    
    Integer rowIndex = 0;

    @ResponseBody
    @PostMapping("/uploadExcelFile")
    public JSONObject uploadExcelFile( MultipartFile excelFile) throws IOException {
        JSONObject result = new JSONObject();
        result.put("PathVariable" ,excelFile.getName());
//      读取上传的文件
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(excelFile.getInputStream());
        XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
        System.out.println("读取Excel文件:"+excelFile);
        System.out.println("读物Sheet:"+sheet.getSheetName());

        sheet.forEach(i->{
            System.out.println(i.getCell(0).getStringCellValue());
            System.out.println(i.getCell(1).getNumericCellValue());
            System.out.println(i.getCell(2).getNumericCellValue());
            Employee employee = new Employee();
            employee.setName(i.getCell(0).getStringCellValue());
            employee.setAge( (int)i.getCell(1).getNumericCellValue());
            employee.setSalary((float) i.getCell(2).getNumericCellValue());
//          将Excel里的数据遍历出来放入DB
            employeeService.saveEmployee(employee);
        });

        xssfWorkbook.close();
        return result;
    }

    /**
     * 下载Excel
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/listAllEmployeeOfExcel")
    public void listAllEmployeeOfExcel(HttpServletRequest request , HttpServletResponse response) throws IOException {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("Sheet1");
//      读取employee、放入Excel
        employeeService.findAll().forEach(i->{
        XSSFRow row = sheet.createRow(rowIndex++);
        XSSFCell cell = row.createCell(0);
        XSSFCell cell2 = row.createCell(1);
        XSSFCell cell3 = row.createCell(2);
        cell.setCellValue(i.getName());
        cell2.setCellValue(i.getAge());
        cell3.setCellValue(i.getSalary());

        });
//      response输出流设置
        response.reset();
        response.setHeader("Content-disposition","attachment; filename=Employee.xlsx");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        workbook.write(response.getOutputStream());
        workbook.close();

    }
}

视图页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    首頁


一個普通的是視圖葉!

Hello everyone


say hello
帶一個參數的請求Action
也是帶一個參數的Action?
测试Repository

下载Excel文件

测试

普通Web项目架构渐进式实现--SSM_第10张图片
普通Web项目架构渐进式实现--SSM_第11张图片
普通Web项目架构渐进式实现--SSM_第12张图片
上传成功。
开始测试导出数据到Excel并下载。
普通Web项目架构渐进式实现--SSM_第13张图片
普通Web项目架构渐进式实现--SSM_第14张图片

你可能感兴趣的:(spring,ehcache3,poi,spring,cache,mybatis)