SSM框架整合简述

现在最流行的应该就是 SSM 了,SSH 一去不复返,SSM 指的是:SpringMVC + Spring + MyBatis
弃用 Struts2 的原因在于漏洞比较多吧(毕竟人用的也多),危害还比较大,官方实力甩锅
至于弃用 Hibernate 倒不是它不好,因为太大了,对于小项目用不着这么重量级的东西,MyBatis 这种轻量级的框架是很适合的,并且性能也好
不得不说,相比 SSH 来说,SSM 整合确实更加简单,错觉?

Spring和MyBatis整合

万年不变定律,先导包,除了 Spring 和 MyBatis 的基本包,别忘了还有一个 MyBatis-spring 的“插件包”,应该也是需要连接池的吧,除了 C3P0 还可以选择 bonecp,据说它的性能比 C3P0 还高,如果用它就别忘了导入 bonecp-spring 的依赖,我的栗子用的 Maven 构建的,pom 文件一览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bfchengnuo</groupId>
<artifactId>FirstSSM</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>

<properties>
<!-- spring版本号 -->
<spring.version>5.0.0.RELEASE</spring.version>
<!-- mybatis版本号 -->
<mybatis.version>3.4.5</mybatis.version>
<!-- log4j日志文件管理包版本 -->
<slf4j.version>1.7.25</slf4j.version>
</properties>

<dependencies>
<!--测试、日志依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>

<!-- spring 相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
<!--MyBatis 相关依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.0</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<!--整合相关-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 连接池-->
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp-spring</artifactId>
<version>0.8.0.RELEASE</version>
</dependency>
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Apache工具组件 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<!--JSP 相关-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

<build>
<finalName>FirstSSM</finalName>
<plugins>
<!--设置编译版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>

这样大部分依赖就处理完了,接下来就是配置 Spring、MyBatis 的环境,就是写写配置文件,这里我拆了一下,分成 jdbc.properties 、log4j.properties 、applicationContext.xml 、applicationContext-mybatis.xml 等几个文件:
日志:

1
2
3
4
5
log4j.rootLogger=DEBUG,A1
log4j.logger.org.mybatis = DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

applicationContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<context:property-placeholder location="classpath:jdbc.properties"/>

<!--配置扫描器-->
<context:component-scan base-package="com.bfchengnuo" />

<!--设置数据源-->
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
destroy-method="close">
<!-- 数据库驱动 -->
<property name="driverClass" value="${jdbc.driver}"/>
<!-- 相应驱动的jdbcUrl -->
<property name="jdbcUrl" value="${jdbc.url}"/>
<!-- 数据库的用户名 -->
<property name="username" value="${jdbc.username}"/>
<!-- 数据库的密码 -->
<property name="password" value="${jdbc.password}"/>
<!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
<property name="idleConnectionTestPeriodInMinutes" value="60"/>
<!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
<property name="idleMaxAgeInMinutes" value="30"/>
<!-- 每个分区最大的连接数 -->
<property name="maxConnectionsPerPartition" value="150"/>
<!-- 每个分区最小的连接数 -->
<property name="minConnectionsPerPartition" value="5"/>
</bean>
</beans>

applicationContext-mybatis (数据源的定义我想抽在这里也许也比较好):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 结合Spring和Mybatis -->
<bean id = "sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource" />
<!--指定MyBatis的配置文件-->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<!-- 自动扫描mapping.xml文件 -->
<property name = "mapperLocations" value="classpath:mybatis/mappers/UserMapper.xml" />
<!--设置别名扫描-->
<property name="typeAliasesPackage" value="com.bfchengnuo.pojo" />
</bean>

<!-- 扫描包,Spring 会自动查找其下的类(mapper),将其实例化 -->
<bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name = "basePackage" value="com.bfchengnuo.mapper" />
<!-- 多数据源的时候使用下面指定 -->
<!--<property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactory" />-->
</bean>
</beans>

基本实现了自动化,根据定义的接口自动实例化 Mapper ,通过包扫描的方式来自动扫描定义的接口,这样不需要使用 getMapper 方法了,也不需要自己在 beans 中一个个的定义 Mapper 了。
其中引用了 MyBatis 的配置文件,这个文件的大部分内容都可以在 spring 中配置了,少数几个还不行,比如 Settings、插件等还是需要配置在 MyBatis 的配置文件中才行:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启驼峰映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

这样配置文件就算是写完了,接下来就是写几个 mapper 映射文件和 mapper 对应的接口去测试一下了,详情参考 Github

与SpringMVC整合

这个就更简单了,SpringMVC 本来就是 spring 的子项目,可以说是无缝整合,不需要其他任何的插件包,直接导入 spring-web 包就可以用了,记得写写配置文件就行:
firstssm-servlet.xml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">

<!--定义注解驱动-->
<mvc:annotation-driven />

<!--controller 扫描包-->
<context:component-scan base-package="com.bfchengnuo.controller" />

<!--定义视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 最后有 / 啊! -->
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

<!--避免静态资源被拦截-->
<mvc:default-servlet-handler />
</beans>

然后把关键的 web.xml 文件搞一搞就完事了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<display-name>FirstSSM</display-name>

<!--手动指定 spring 文件的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<!--Spring的ApplicationContext 载入 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 编码过滤器,以UTF8编码,处理POST方式提交的乱码 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--Spring MVC 配置-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 自定义SpringMVC配置文件路径 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/firstssm-servlet.xml</param-value>
</init-param>
<!-- 随容器自动启动完成初始化 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 拦截所有的请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

因为配置的是拦截所有的请求,所以记得在 springmvc 的配置文件中放行静态资源,要不然 css、js 都是会 404 的

测试

然后就可以写个简单的栗子看看行不行了,来看定义的 controller :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RequestMapping("user")
@Controller
public class UserController {
@Resource
private UserService userService;

// 临时测试
@RequestMapping("users")
public String toUsers() {
return "users";
}

@RequestMapping("getList")
public ModelAndView getUserList() {
ModelAndView mv = new ModelAndView("users");
List<User> users = userService.queryUserList();
mv.addObject("userList",users);
return mv;
}
}

有了自动包扫描就是方便,只需要配置一个 @Controller 注解就可;然后是 service,很薄:

1
2
3
4
5
6
7
8
9
@Service
public class UserService {
@Resource
private UserMapper userMapper;

public List<User> queryUserList() {
return userMapper.queryUserList();
}
}

省下的 mapper 接口和相应的映射文件就不贴了,完整代码:Github

关于外部配置文件

有时候需要从 properties 文件中加载配置,最原始的方案是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- 允许JVM参数覆盖 -->
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<!-- 忽略没有找到的资源文件 -->
<property name="ignoreResourceNotFound" value="true" />
<!-- 配置资源文件 -->
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>

到后来是这样的(Spring 也推荐这样用):

1
<context:property-placeholder location="classpath:spring/jdbc.properties" />

以上两种方式,在 bean 定义中依然可以通过“${}”这种方式来取值。
<context:property-placeholder/> 这个基于命名空间的配置,其实内部就是创建一个 PropertyPlaceholderConfigurerBean 而已,并且它有一个“有趣”的现象:
使用分散配置的方式,在两个模块中都引入了这个标签,单独运行某个模块没问题,当一起运行时就会报 Could not resolve placeholder... 的错误。

原因:
Spring 容器采用反射扫描的发现机制,在探测到 Spring 容器中有一个 PropertyPlaceholderConfigurer 的 Bean 就会停止对剩余PropertyPlaceholderConfigurer 的扫描(Spring 3.1 已经使用 PropertySourcesPlaceholderConfigurer 替代PropertyPlaceholderConfigurer 了)。
换句话说,即 Spring 容器仅允许最多定义一个 PropertyPlaceholderConfigurer (或 <context:property-placeholder/> ),其余的会被 Spring 忽略掉

至于如何解决,我找到了两种,第一种就是不使用分散配置了,集合读取:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>  
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:property-placeholder location="classpath*:conf/conf*.properties"/>
<import resource="a.xml"/>
<import resource="b.xml"/>
</beans>

这种方式应该算是比较优雅的了,如果有特殊需求,不能使用这种方式,可以用第二种来“强制”支持多个pp标签:

1
2
3
<context:property-placeholder order="1" location="classpath*:conf/conf_a.properties" ignore-unresolvable="true"/>
<!-- 这两个配置可能不在一个文件中 -->
<context:property-placeholder order="2" location="classpath*:conf/conf_b.properties" ignore-unresolvable="false"/>

首先来解释下这两个属性:

  • ignore-unresolvable
    单独使用来看是“是否忽视不存在的配置项”,不仅如此,经过测验,有一个隐含意思:是否还要扫描其他配置项:如果扫描到的为 false,则会忽视后续的 property-placeholder
    默认值为 false
  • order
    会反应出顺序,值越小优先级越高即越早执行

也就是说,当配置多个 property-placeholder 的时候,要配置 order,并且最后一个的 ignore-unresolvable 要保证为 false,其他的为 true

关于事务

上面的基本配置中应该是没有加入事务管理的,除去纯 AOP 的配置,常用的还有“声明式”和“编程式”,他们两个是可以共同使用的。

1
2
3
4
5
6
7
8
9
<!-- 配置事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<!-- 标识事务唯一,配合注解的 value 在多事务管理器的场景下使用 -->
<!-- <qualifier value=""/> -->
</bean>

<!-- 默认使用JDK的接口代理,如果没有接口就不起作用 proxy-target-class=false -->
<tx:annotation-driven transaction-manager="tx" proxy-target-class="true" />

这是“声明式”事务的简单配置,这样可以在要启用事务的方法上直接使用 @Transactional 注解来开启事务,然后通过 propagation 属性可以配置其传播特性等,只需要注意下嵌套方法调用的情况就好了。
还有就是事务一般是加在 Service 层,加在 Controller 层会失效,起码和 SpringMVC 时是这样,这里和父子容器有点关系,还有就是在扫描时注意子容器的范围,这个在 Github 已经做了笔记。
然后下面就来介绍“编程式”事务:

1
2
3
4
5
6
<!-- transactionManager 配置和上面一样,省略 -->

<!-- 编程式事务 -->
<bean id="tt" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="tx" />
</bean>

其实就是配置了个 TransactionTemplate,然后在代码中手动调用 TransactionTemplate 来管理事务,这样能解决多线程下 @Transactional 事务失效的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private TransactionTemplate template;

public void save3() {
template.execute((status) -> {
// TransactionStatus - status 参数:事务开启、回滚等状态
User user = new User();
user.setId(UUID.randomUUID().toString());
user.setUsername("Nxuan");
// int x = 1/0;
return userMapper.add(user);
});
}

最后,如果不想写 @Transactional 注解,可以使用纯 AOP 的配置方式,简单贴下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!--定义事务策略 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 所有以 query 开头的都是只读的 -->
<tx:method name="query*" read-only="true" />
<!-- 其他方法默认 -->
<tx:method name="*" />
</tx:attributes>
</tx:advice>

<!-- 定义一个切面 -->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.bfchengnuo.manage.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />
</aop:config>

不过这种总体来说用的不算多,但是某些情景下是非常实用的。

参考

http://www.iteye.com/topic/1131688

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~