当下微服务大行其道,以SpringBoot为核心的软件架构逐渐成为各类型后台架构的主体,但是这不意味着SSH、SSM式的架构马上就要被淘汰。相反,我认为微服务架构和传统Web架构应用场景很多并不冲突,在现在可见的几年后SSM这式架构依然会大行其道。趁着我现在有参入到SSM的项目,正好把之前的记录梳理一遍。这篇文章是渐进式的。首先我会给出一个SpringMVC、Mybatis的Demo,然后我会逐渐加入各类组件进去,数据库连接池,二级缓存,Spring代理SqlSession事务、Thymeleaf模板引擎等。最后时间允许的话就加入POI整合成一个报表程序。
工具:
此项目的Git仓库
更新日志
架构更新日志
<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>
<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>
<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
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>
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-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
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 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>
接口中的方法签名要与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();
}
}
SpringMVC返回JSON或者其它类对象需要做些配置。
加入依赖:
com.fasterxml.jackson.core
jackson-databind
2.9.9.2
修改SpringMVC的上下文配置文件:
这样就可以正常转换对象返回输出了。
加入依赖:
com.mchange
c3p0
0.9.5.4
需要写一个数据源工厂类的子类:
public class MybatisC3p0DatabaseSourcesFactory extends UnpooledDataSourceFactory {
public MybatisC3p0DatabaseSourcesFactory(){
this.dataSource = new ComboPooledDataSource();
}
}
如果是Mybatis使用配置文件几个参数需要修改:
单独的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的数据源已经是由c3p0管理了。可以在启动时看到相关日志。
其实mybatis本身也有连接池。
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的缓存实现
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可以使用配置文件创建缓存,也可以摆脱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工厂
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() {
}
}
一般缓存查询数据都是放到一个过程里:请求数据,到缓存查,没找到再到数据库里查,在数据库查找的同时放到缓存里,之后都在缓存里了。
为了表述清晰我把这个步骤拆成了两个,方便新手理解。
不要忘记配置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,另外做些拓展。
单独使用mybatis是有很多限制的(比如无法实现跨越多个session的事务),而且很多业务系统本来就是使用spring来管理的事务,因此mybatis最好与spring集成起来使用。
首先是加入相关依赖
org.mybatis
mybatis-spring
2.0.2
org.springframework
spring-jdbc
5.1.9.RELEASE
这一步用来测试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了。
声明式事务管理
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");
}
}
删除51–52
删除53–60
因为表里没有第55条记录,抛出了IllegalStateException异常,事务回滚。
事务生效,测试成功。
到现在为止,我们已经用Spring代理了由C3P0做连接池的数据源、SqlSessionFactory,还加了声明事事务管理。但是整体来看只有声明式的事务管理给我们以后的开发减少了代码量,反观获得数据库会话这块并没有给我带来明显的方便,那么我们现在要开始着手做SqlSession的完全代理了。
一个组件被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;
}
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的配置文件还维持之前的不改动。
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());
}
}
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
/**
* 写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();
}
为了让大家更好的了解Spring Cache和POI的作用,我把它俩整到一块做个例子。
Spring MVC上下文配置文件:
1024000
要注意@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();
}
java.lang.String
java.util.List
35
64
1
2
@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文件