1. Spring入门
首先要了解一下什么是spring。通过一个简单的样例来分析入门。
首先的自学必备,官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/
1.1 Spring简介
- spring框架是以interface21框架为基础,重新设计丰富的成果。
- spring就是整合了很多框架的一个工具
- spring是一个免费的开源框架(容器)。
- spring是一个轻量级的,非侵入式的框架。
- spring核心:可控制反转(IOC),面向切面(AOP)。
- 支持事务的处理。
spring这么牛逼,官网也这样介绍的:https://spring.io/why-spring。
反正一起吹spring牛逼就对了。
官方文档的介绍顺序是:
Overview
history, design philosophy, feedback, getting started.
概述:讲一些历史,设计理念什么的
Core
IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.
核心:讲IOC和AOP,事件和配置
Testing
Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient.
测试:MVC
Data Access
Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling.
数据访问:事务,JDBC,XML
Web Servlet
Spring MVC, WebSocket, SockJS, STOMP Messaging.
WebServlet:SpringMVC等内容
Web Reactive
Spring WebFlux, WebClient, WebSocket.
Web:WenSocket等内容
Integration
Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching.
集成:邮件,任务调度,缓存
Languages
Kotlin, Groovy, Dynamic Languages.
语言:动态语言什么的
概述我们大概掠过。
打开官方文档的Core,一开始就是IOC容器介绍。(由于都是英文,我英语又不好,就翻译成中文看了)下面就不放英文原文了,有兴趣的同学可以去自己看一下。
1.2 环境配置
没什么环境要配的,轻量级的spring只需要导一下maven依赖就好了。
直接导webmvc的,一次到位
1
2
3
4
5
6
7
8 1<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
2<dependency>
3 <groupId>org.springframework</groupId>
4 <artifactId>spring-webmvc</artifactId>
5 <version>5.2.4.RELEASE</version>
6</dependency>
7
8
和一个整合:
1
2
3
4
5
6
7
8
9
10 1<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
2<dependency>
3 <groupId>org.springframework</groupId>
4 <artifactId>spring-test</artifactId>
5 <version>5.2.4.RELEASE</version>
6 <scope>test</scope>
7</dependency>
8
9
10
1.3 简单入门
我们首先进行一个简单的小样例,然后通过样例来分析spring。
通常情况下我们的三层实现是这样的:
-
pojo一个实体类。
-
dao实现接口和方法
1
2
3
4
5
6
7 1public class UserDaoImpl implements UserDao {
2 public void findAll() {
3 System.out.println("dao实现");
4 }
5}
6
7
-
service层调用dao层
1
2
3
4
5
6
7
8
9
10
11 1import com.admin.dao.UserDao;
2import com.admin.dao.UserDaoImpl;
3
4public class UserServiceImpl implements UserService {
5 UserDao userDao = new UserDaoImpl();
6 public void findAll() {
7 userDao.findAll();
8 }
9}
10
11
-
用户层调用service层:
1
2
3
4
5
6
7
8
9
10 1import com.admin.service.UserService;
2import com.admin.service.UserServiceImpl;
3public class MyTest {
4 public static void main(String[] args) {
5 UserService userService = new UserServiceImpl();
6 userService.findAll();
7 }
8}
9
10
这样的三层架构,使得程序的耦合性非常高。用new 的方法创建对象,需要对象的实现类。修改任何一个实现,我们都需要将所有的代码修改好,不小心还可能出现很多的bug。
为了避免这样的情况,需要换一种思路,这种思路就是IOC思想,利用IOC思想来实现程序,通过set方法来实现修改参数。(IOC思想后面在讨论)
上面的可修改为:
-
dao层不变。
-
service修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1import com.admin.dao.UserDao;
2public class UserServiceImpl implements UserService {
3 UserDao userDao;
4 public UserDao getUserDao() {
5 return userDao;
6 }
7 public void setUserDao(UserDao userDao) {
8 this.userDao = userDao;
9 }
10 public void findAll() {
11 userDao.findAll();
12 }
13}
14
15
-
在resources中配置ApplicationContext.xml(官方文档1.2.1模板,参考1.2.2配置该xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 https://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl"/>
8
9 <bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
10 <property name="userDao" ref="UserDaoImpl"/>
11 </bean>
12
13</beans>
14
15
-
用户层:使用我们配置的IOC容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1import com.admin.service.UserService;
2import org.springframework.context.ApplicationContext;
3import org.springframework.context.support.ClassPathXmlApplicationContext;
4
5public class MyTest {
6
7 public static void main(String[] args) {
8 ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
9 UserService userService =(UserService) context.getBean("UserServiceImpl");
10 userService.findAll();
11 }
12}
13
14
bean的格式:
- id:唯一标识符。
- class:指向目标的全限定类名。
- property:赋值。其中name表示的是赋值对象id,ref表示引用类型,value表示基本数据类型(int,string)。
1.4 IOC初体验
IOC,全称:Inversion of Control,控制反转。
1.4.1
什么是控制反转?
就是说,我们对程序的控制,不再是程序主动控制了,而是人为控制程序的动作。
比如,一开始都是程序需要什么,我们就new一个对象给程序。
现在是,我们给程序了什么,程序用什么。
注意一下我上面那段话的意思。
前者意思是,人需要按照程序来走,就像从A地到B地,我们只先修了一个铁路,就需要一个火车,只有公路,就需要一个汽车。需要什么是写程序时决定的,我们所作的事情就是获得这个需要的对象。
而后者的意思就是,从A地到B地,我们想通过汽车,就可以走公路,我们想坐火车,就走铁路就好了。就是说,路是准备好的,我们只需要自己随意定义工具就好了。
以上是关于控制反转的理解。(个人总结,如有错误欢迎指正)
1.4.2 什么是IOC容器?
官网给了一个图来解释:
大概意思就是pojo和控制元数据都是被Spring容器统一掌握,然后分配使用。
从我们上面的入门样例可以看出来,我们把所有东西都交给了Spring容器来管理。
这个bean就是一个容器,将所有对象交给bean来管理。
需要什么就通过bean来获得。不需要再new东西了。
可以把bean理解成一个map,通过key来获得value。
我们所作的就是修改配置文件就好了。配置文件说白了就是一个记事本,记录着配置,完全可以不用操作源码了。
比如:我们在bean中配置了UserDaoImpl和UserServiceImpl,
那么我们在test中,直接new出来这个bean容器。用到了UserService,直接从bean中传入目标key,然后就获得了UserServiceImpl对象。
同样道理,在UserService中需要用到了UserDao,我们在bean中配置了目标UserDao,那么我们就直接会通过无参构造来获得。
期间我们不用管bean是如何创建对象的,这已经和我们没有关系了,我们只需要修改对应的bean即可。
这就是关于IOC容器的理解。
2.IOC容器
2.1 Bean总览
Spring IOC容器管理一个或多个bean。这些bean是通过我们对bean的配置创建的(<bean/>)
这些容器中默认包含一下元数据:
- 全限定类名:该bean的实现类
- bean的配置元素,用于声明bean在容器中的行为(作用域,生命周期,等)
- 引用其他bean(依赖)。
- 新建对象中设置其他配置(管理连接池bean的池大小,链接数等限制)。
这个表可以让我们很明确的知道各个属性都干了什么:
Class
全限定类名
Name
bean的唯一标识符
Scope
作用域
Constructor arguments
构造函数参数
Properties
属性
Autowiring mode
自动连接模式
Lazy initialization mode
延迟初始化模式
Initialization method
初始化方法(回调)
Destruction method
销毁方法(回调)
2.2 IOC容器创建对象的方式:DI
依赖注入(DI),全称Dependency Injection。它是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法参数,创建对象后的设置等方式来定义其依赖关系。
2.2.1 依赖解析
在IOC容器执行bean依赖解析:
- 使用ApplicationContext.xml来配置和初始化所有bean的元数据(通过xml,注解,Java代码等方式)。
- 对于每个bean,都要在创建bean时将这些依赖项提供给bean
- 每个属性(构造参数)都要设置其值,或是对IOC容器中另一个bean的引用。
创建容器时的活动:
- spring容器会验证每个bean的配置,但不会立刻创建bean。
- 先创建具有单例作用域并设置为预先实例化的bean。
- 创建bean会创建bean的依赖。(循环依赖会报错)。
2.2.1 基于构造函数的依赖注入。
-
无参构造创建对象。上面我们用的就是无参构造创建对象,也是默认的创建方法。
-
有参构造创建对象。
-
通过下标赋值
1
2
3
4
5 1<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
2 <constructor-arg index="0" ref="UserDaoImpl"/>
3</bean>
4
5
index表示下标索引,ref表示引用对象。
1
2 1* 通过类型赋值
2
1
2
3
4
5 1<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
2 <constructor-arg type="com.admin.dao.UserDao" ref="UserDaoImpl"/>
3</bean>
4
5
type:全限定类名,参数类型是什么,就传什么。ref:引用对象
1
2 1* 通过参数名赋值
2
1
2
3
4
5 1<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
2 <constructor-arg name="userDao" ref="UserDaoImpl"/>
3</bean>
4
5
name:对象名,ref:引用对象。
2.2.2 基于set的依赖注入。
set的DI是通过在调用无参(有参)构造bean之后,通过调用bean上的setter方法来实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1public class ExampleBean {
2 private AnotherBean beanOne;
3 private YetAnotherBean beanTwo;
4 private int i;
5
6 public void setBeanOne(AnotherBean beanOne) {
7 this.beanOne = beanOne;
8 }
9 public void setBeanTwo(YetAnotherBean beanTwo) {
10 this.beanTwo = beanTwo;
11 }
12 public void setIntegerProperty(int i) {
13 this.i = i;
14 }
15}
16
17
对于上面那个类,其bean的set配置法:通过set方法注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1<bean id="anotherExampleBean" class="examples.AnotherBean"/>
2<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
3
4<bean id="exampleBean" class="examples.ExampleBean">
5 <!-- setter injection using the nested ref element -->
6 <property name="beanOne">
7 <ref bean="anotherExampleBean"/>
8 </property>
9
10 <!-- setter injection using the neater ref attribute -->
11 <property name="beanTwo" ref="yetAnotherBean"/>
12 <property name="integerProperty" value="1"/>
13</bean>
14
15
2.2.3 依赖注入的详细配置。
对于9种类型的注入:
bean | ref | idref | list | set | map | props | value | null
-
配置默认bean
1
2
3
4
5
6
7
8
9 1<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
2<!-- results in a setDriverClassName(String) call -->
3 <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
4 <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
5 <property name="username" value="root"/>
6 <property name="password" value="masterkaoli"/>
7</bean>
8
9
-
使用ref:我们上面一直在用,引用类型
-
使用idref
1
2
3
4
5
6
7
8
9
10
11
12
13 1<bean id="theTargetBean" class="..."/>
2
3<!--下面两个的效果相同-->
4<bean id="theClientBean" class="...">
5 <property name="targetName">
6 <idref bean="theTargetBean"/>
7 </property>
8</bean>
9<bean id="client" class="...">
10 <property name="targetName" value="theTargetBean"/>
11</bean>
12
13
-
使用list
1
2
3
4
5
6
7
8
9
10
11 1<bean id="moreComplexObject" class="example.ComplexObject">
2 <!-- results in a setSomeList(java.util.List) call -->
3 <property name="someList">
4 <list>
5 <value>a list element followed by a reference</value>
6 <ref bean="myDataSource" />
7 </list>
8 </property>
9</bean>
10
11
-
使用set:
1
2
3
4
5
6
7
8
9
10
11 1<bean id="moreComplexObject" class="example.ComplexObject">
2 <!-- results in a setSomeSet(java.util.Set) call -->
3 <property name="someSet">
4 <set>
5 <value>just some string</value>
6 <ref bean="myDataSource" />
7 </set>
8 </property>
9</bean>
10
11
-
使用map:entry种设置key和value
1
2
3
4
5
6
7
8
9
10
11 1<bean id="moreComplexObject" class="example.ComplexObject">
2 <!-- results in a setSomeMap(java.util.Map) call -->
3 <property name="someMap">
4 <map>
5 <entry key="an entry" value="just some string"/>
6 <entry key ="a ref" value-ref="myDataSource"/>
7 </map>
8 </property>
9</bean>
10
11
-
使用property:配置文件中key和value
1
2
3
4
5
6
7
8
9
10
11
12 1<bean id="moreComplexObject" class="example.ComplexObject">
2 <!-- results in a setAdminEmails(java.util.Properties) call -->
3 <property name="adminEmails">
4 <props>
5 <prop key="administrator">administrator@example.org</prop>
6 <prop key="support">support@example.org</prop>
7 <prop key="development">development@example.org</prop>
8 </props>
9 </property>
10</bean>
11
12
-
使用value:上面的任意样例都使用了value
-
使用null:直接一个null标签就可以了
1
2
3
4
5
6
7 1<bean class="ExampleBean">
2 <property name="email">
3 <null/>
4 </property>
5</bean>
6
7
2.2.4 扩展依赖注入配置:c,p命名空间
首先导入对应的命名空间:
1
2
3
4
5 1<beans xmlns:p="http://www.springframework.org/schema/p"
2 xmlns:c="http://www.springframework.org/schema/c"
3 ...>
4
5
之后就可以使用了:
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 1<beans xmlns="http://www.springframework.org/schema/beans"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:c="http://www.springframework.org/schema/c"
4 xmlns:p="http://www.springframework.org/schema/p"
5xsi:schemaLocation="http://www.springframework.org/schema/beans
6 https://www.springframework.org/schema/beans/spring-beans.xsd">
7 <!--普通的和使用命名空间的比较-->
8
9<!--使用c-namespace比较-->
10 <bean id="beanTwo" class="x.y.ThingTwo"/>
11 <bean id="beanThree" class="x.y.ThingThree"/>
12
13 <bean id="beanOne" class="x.y.ThingOne">
14 <constructor-arg name="thingTwo" ref="beanTwo"/>
15 <constructor-arg name="thingThree" ref="beanThree"/>
16 <constructor-arg name="email" value="something@somewhere.com"/>
17 </bean>
18 <bean id="beanOne" class="x.y.ThingOne"
19 c:thingTwo-ref="beanTwo"
20 c:thingThree-ref="beanThree"
21 c:email="something@somewhere.com"/>
22
23<!--使用p-namespace比较-->
24 <bean name="jane" class="com.example.Person">
25 <property name="name" value="Jane Doe"/>
26 </bean>
27
28 <bean name="john-classic" class="com.example.Person">
29 <property name="name" value="John Doe"/>
30 <property name="spouse" ref="jane"/>
31 </bean>
32 <bean name="john-modern" class="com.example.Person"
33 p:name="John Doe"
34 p:spouse-ref="jane"/>
35</beans>
36
37
2.3 bean的作用域
官方给了一个完整的表格:
singleton
(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype
Scopes a single bean definition to any number of object instances.
request
Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session
Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application
Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket
Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
后四个是和web有关的,在MVC处涉及。
前两个
- singleton:单例,就是一个bean被多个bean引用,这些对象是相同的。默认是单例状态
- prototype:原型,就是一个bean被多个bean引用,分别是不同的对象。
可以写一下xml和测试类测试一下:
pojo:
1
2
3
4
5
6
7
8 1public class Address {
2 private String address;
3}
4public class Student {
5 private String name;
6}
7
8
bean:
1
2
3
4
5
6 1<bean id="Address" class="com.admin.pojo.Address" scope="prototype"/>
2<bean id="Student" class="com.admin.pojo.Student" scope="singleton">
3 <property name="name" value="admin"/>
4</bean>
5
6
test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1import com.admin.pojo.Address;
2import com.admin.pojo.Student;
3import org.springframework.context.ApplicationContext;
4import org.springframework.context.support.ClassPathXmlApplicationContext;
5
6public class MyTest {
7 public static void main(String[] args) {
8 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
9 Student student = context.getBean("Student",Student.class);
10 Student student1 = context.getBean("Student",Student.class);
11 Address address1 = context.getBean("Address",Address.class);
12 Address address2 = context.getBean("Address",Address.class);
13 System.out.println(student==student1);
14 System.out.println(address1==address2);
15 }
16}
17
18
可以看出他们的异同。
2.4 bean的自动装配
2.4.1 基于xml的自动装配
我们可以通过配置autowire属性来减少配置文件的书写。
bean的自动装配主要分为byName和byType。
-
byName:通过名字自动配置。名字必须相同,大小写也要相同。
-
byType:通过类型自动装配。类型必须保证唯一。
1
2
3
4
5
6
7
8
9 1<bean id="Address" class="com.admin.pojo.Address">
2 <property name="address" value="123"/>
3</bean>
4
5<bean id="Student" class="com.admin.pojo.Student" autowire="byType">
6 <property name="name" value="admin"/>
7</bean>
8
9
1
2
3
4
5
6
7
8
9 1public class MyTest {
2 public static void main(String[] args) {
3 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
4 Student student = context.getBean("Student",Student.class);
5 System.out.println(student);//输出:Student{name='admin', address=Address{address='123'}}
6 }
7}
8
9
2.4.2 基于注解的自动装配
jdk1.5支持注解,spring2.5支持注解。
配置注解环境:导入约束,配置注解的支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 https://www.springframework.org/schema/beans/spring-beans.xsd
7 http://www.springframework.org/schema/context
8 https://www.springframework.org/schema/context/spring-context.xsd">
9
10 <context:annotation-config/>
11
12</beans>
13
14
之后直接可以在属性上使用@Autowired来进行自动装配。多个同类型时,可以使用Qualifier指定装配id(需要同类型)
1
2
3
4
5
6
7
8 1public class Student {
2 private String name;
3 @Autowired
4 @Qualifier(value = "Address2")
5 private Address address;
6}
7
8
1
2
3
4
5
6
7
8
9
10
11
12 1<bean id="Address" class="com.admin.pojo.Address">
2 <property name="address" value="123"/>
3</bean>
4<bean id="Address2" class="com.admin.pojo.Address">
5 <property name="address" value="123321"/>
6</bean>
7
8<bean id="Student" class="com.admin.pojo.Student">
9 <property name="name" value="admin"/>
10</bean>
11
12
在测试类中输出测试:
1
2
3
4
5
6
7
8
9 1public class MyTest {
2 public static void main(String[] args) {
3 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
4 Student student = context.getBean("Student",Student.class);
5 System.out.println(student);//Student{name='admin', address=Address{address='123321'}}
6 }
7}
8
9
还有一种直接一个注解Resource就可以的。在javax.annotation.Resource中。
jdk中没有的,取maven仓库导一下javax.annotation就可以了。
1
2
3
4
5
6
7
8
9 1public class Student {
2 private String name;
3// @Autowired
4// @Qualifier(value = "Address2")
5 @Resource(name = "Address2")
6 private Address address;
7}
8
9
3. Spring配置
3.1 别名
1
2
3 1<alias name="user" alias="userNew"/>
2
3
在调用中,userNew和user都会返回user对象,就是起一个其他称呼。
3.2 bean配置
1
2
3 1<bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl" name="dao dao1,dao2;dao3"/>
2
3
id和class之前我们题到了,这里多了一个name,也是别名的意思,还可以同时取多个别名,中间可以用",“或者” “或者”;"等符号分割。
3.3 import
团队开发一个项目,分工不同的人写不同的类,有不同的bean我们需要将其汇总到一个总的bean中,然后只获得总的bean就好了。
官方文档:1.2.1中提到了如果多个xml,我们如何将其整到一块。
- A:bean1.xml
- B:bean2.xml
- C:bean3.xml
在总的bean中就可以了:
1
2
3
4
5 1<import resource="bean1.xml"/>
2<import resource="bean2.xml"/>
3<import resource="bean3.xml"/>
4
5
4. 使用注解实现bean
在官方文档1.9和1.10处将的非常详细,我就不抄了,把我认为的终点总结一下吧。
官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/core.html
可以现在xml配置文件中配置支持注解和扫描的包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 https://www.springframework.org/schema/beans/spring-beans.xsd
7 http://www.springframework.org/schema/context
8 https://www.springframework.org/schema/context/spring-context.xsd">
9
10 <context:component-scan base-package="com.admin.pojo"/>
11 <context:annotation-config/>
12
13</beans>
14
15
之后就可以自由使用注解来进行操作了。
4.1 自动装配的注解@Autowired和@Required
在xml中配置扫描的包,就可以直接使用注解来实现自动装配了。
上面已经介绍过了,下面把例子再抄一份。
- @Autowired:spring的自动配置bean,配合@Qualifier和@Primary(主要自动添装)使用
该注解可以在属性,构造函数,set上使用。
1
2
3
4
5
6
7
8 1public class Student {
2 private String name;
3 @Autowired
4 @Qualifier(value = "Address2")
5 private Address address;
6}
7
8
-
@Resource:java自带的自动配置。
1
2
3
4
5
6
7
8 1public class Student {
2 private String name;
3
4 @Resource(name = "Address2")
5 private Address address;
6}
7
8
4.2 属性的注入@Component
在类和属性上加一些注解就可以实现注解的注入。
-
@Component:将一个类装配到spring容器中,效果等同配置中的:
1
2
3 1<bean id = "..." class = "..." />
2
3
在MVC三层架构中,不同层使用的注解有不同的标识,作用都是一样的(将某个类装配到spring容器中)目的只是为了区分不同的层。
-
@Respository:用在dao层的注解
- @Service:用在Service层的注解
- @Controller:用在controller层的注解
-
@Value(String key):效果等同
1
2
3 1<property name = "name" value = "value"/>
2
3
使用样例:
1
2
3
4
5
6
7 1@Component
2public class User {
3 @Value("aaa")
4 public String name;
5}
6
7
4.2 作用域的配置@Scope
可以直接使用@Scope注解进行作用域的配置
我们上面说的单例和原型。是可以通过这个注解来配置的。@Scope(String);
官方给了一个原型的作用域。
1
2
3
4
5
6
7 1@Scope("prototype")
2@Repository//表示是一个dao层的注解效果等同@Component
3public class MovieFinderImpl implements MovieFinder {
4 // ...
5}
6
7
4.3 使用配置类代替xml的注解
也可以完全抛弃xml,直接使用注解配置。
4.3.1 创建配置类@Configuration和@Bean
在一个主配置类中配置@Configuration和@Bean相当于再xml中配置了spring容器。
-
@Configuration:表示这是一个配置类,就和我们之前看的beas.xml一样。这个类也会被spring容器托管,本来就是@Component。
-
@Bean:注册一个bean,相当于我们写的bean标签。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1import com.admin.pojo.User;
2import org.springframework.context.annotation.Bean;
3import org.springframework.context.annotation.Configuration;
4
5@Configuration
6public class MyConfig {
7
8 @Bean
9 public User getUser(){
10 return new User();
11 }
12}
13
14
15
4.3.2 配置扫描的包@ComponentScan
如果要扫描包,可以在配置类中使用@ComponentScan来描述扫描的包。
1
2
3
4
5
6
7
8
9 1@Configuration
2@ComponentScan(basePackages = "org.example",
3 includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
4 excludeFilters = @Filter(Repository.class))
5public class AppConfig {
6 ...
7}
8
9
效果等同:
1
2
3
4
5
6
7
8
9
10 1<beans>
2 <context:component-scan base-package="org.example">
3 <context:include-filter type="regex"
4 expression=".*Stub.*Repository"/>
5 <context:exclude-filter type="annotation"
6 expression="org.springframework.stereotype.Repository"/>
7 </context:component-scan>
8</beans>
9
10
4.3.3 导入其他配置类@Import
创建另一个配置类MyConfig2.java
1
2
3
4
5
6 1@Configuration
2public class MyConfig2 {
3 //...
4}
5
6
在之前的主配置类中使用@Import来调用
1
2
3
4
5
6
7
8
9
10
11
12 1@Configuration
2@ComponentScan("com.admin.pojo")
3@Import(MyConfig2.class)
4public class MyConfig {
5
6 @Bean
7 public User getUser(){
8 return new User();
9 }
10}
11
12
4.4 测试类中获取容器
由于我们没有通过xml配置spring容器,而是注解,因此我们就不能再用ClassPathXmlApplicationContext类获取容器了,而是与之相应的注解获取容器AnnotationConfigApplicationContext。
1
2
3
4
5
6
7
8
9
10
11 1public class MyTest {
2
3 public static void main(String[] args) {
4 //ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
5 ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
6 User user = context.getBean("getUser",User.class);
7 System.out.println(user);
8 }
9}
10
11
5. AOP
5.1 使用Spring实现AOP
实现spring的AOP操作有很多种,首先先准备一些不变的东西
首先需要导入包
1
2
3
4
5
6
7 1<dependency>
2 <groupId>org.aspectj</groupId>
3 <artifactId>aspectjweaver</artifactId>
4 <version>1.9.5</version>
5</dependency>
6
7
创建一个简单的接口和其实现类.写完之后就不用管它了,就像我们之前学过的动态代理一样,我们不需要对原有的代码做一丝修改.
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 1package com.admin.service;
2
3public interface UserService {
4 void add();
5 void delete();
6 void update();
7 void select();
8}
9
10public class UserServiceImpl implements UserService {
11 public void add() {
12 System.out.println("增加了一个用户");
13 }
14
15 public void delete() {
16 System.out.println("删除了一个用户");
17 }
18
19 public void update() {
20 System.out.println("修改了一个用户");
21 }
22
23 public void select() {
24 System.out.println("查找了一个用户");
25 }
26}
27
28
通过测试类来查看是否完成想要的操作
1
2
3
4
5
6
7
8
9 1public class MyTest {
2 public static void main(String[] args) {
3 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
4 UserService service = context.getBean("UserService",UserService.class);
5 service.add();
6 }
7}
8
9
5.1.1 方法一:通过springAPI来实现动态代理
写一些日志实现:用来做切入操作.这里需要实现spring的代理接口.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1package com.admin.log;
2
3import org.springframework.aop.MethodBeforeAdvice;
4import java.lang.reflect.Method;
5
6public class Log implements MethodBeforeAdvice {
7 public void before(Method method, Object[] objects, Object o) throws Throwable {
8 System.out.println(method.getClass().getName()+"类调用了"+method.getName()+"接口");
9 }
10}
11public class AfterLog implements AfterReturningAdvice {
12 public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
13 System.out.println(method.getClass().getName()+"调用了"+method.getName()+"方法");
14 }
15}
16
17
配置applicationConftext.xml来注册到spring中
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 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:aop="http://www.springframework.org/schema/aop"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans
7 https://www.springframework.org/schema/beans/spring-beans.xsd
8 http://www.springframework.org/schema/context
9 https://www.springframework.org/schema/context/spring-context.xsd
10 http://www.springframework.org/schema/aop
11 https://www.springframework.org/schema/aop/spring-aop.xsd">
12
13 <context:component-scan base-package="com.admin"/>
14 <context:annotation-config/>
15
16 <bean id="UserService" class="com.admin.service.UserServiceImpl"/>
17 <bean id="Log" class="com.admin.log.Log"/>
18 <bean id="AfterLog" class="com.admin.log.AfterLog"/>
19
20 <!--这里就是配置spring的AOP的核心配置-->
21 <aop:config>
22 <!--配置切入点,注意expression表达式 类型 包.类.方法名(参数)-->
23 <aop:pointcut id="PointCut" expression="execution(* com.admin.service.UserServiceImpl.*(..))"/>
24 <!--配置动态代理-->
25 <!--advice-ref指的是添加的方法,pointcut-ref指的是切入点位置-->
26 <aop:advisor advice-ref="Log" pointcut-ref="PointCut"/>
27 <aop:advisor advice-ref="AfterLog" pointcut-ref="PointCut"/>
28 </aop:config>
29</beans>
30
31
测试类种可以测试成功
1
2
3
4
5 1java.lang.reflect.Method类调用了add接口
2增加了一个用户
3java.lang.reflect.Method调用了add方法
4
5
这种方法的好处就是能够更加随意地操作我们的类.只需要实现对应的接口就好了.
我们可以在类中放入更多的东西.
5.1.2 方法二:通过spring自定义切面来实现动态代理
新加入一个自定义切面类,方法名什么都随便写,我这是方便自己些的before和after.
1
2
3
4
5
6
7
8
9
10
11
12 1package com.admin.aspect;
2
3public class MyAspect {
4 public void before(){
5 System.out.println("这是一个前置通知");
6 }
7 public void after(){
8 System.out.println("这是一个后置通知");
9 }
10}
11
12
再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 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:aop="http://www.springframework.org/schema/aop"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans
7 https://www.springframework.org/schema/beans/spring-beans.xsd
8 http://www.springframework.org/schema/context
9 https://www.springframework.org/schema/context/spring-context.xsd
10 http://www.springframework.org/schema/aop
11 https://www.springframework.org/schema/aop/spring-aop.xsd">
12
13 <context:component-scan base-package="com.admin"/>
14 <context:annotation-config/>
15
16 <bean id="UserService" class="com.admin.service.UserServiceImpl"/>
17 <bean id="Log" class="com.admin.log.Log"/>
18 <bean id="AfterLog" class="com.admin.log.AfterLog"/>
19 <bean id="MyAspect" class="com.admin.aspect.MyAspect"/>
20
21 <aop:config>
22 <!--自定义切面,ref表示要引用的类-->
23 <aop:aspect ref="MyAspect">
24 <aop:pointcut id="pointcut" expression="execution(* com.admin.service.UserService.*(..))"/>
25 <!--配置方法出现的位置-->
26 <aop:after method="after" pointcut-ref="pointcut"/>
27 <aop:before method="before" pointcut-ref="pointcut"/>
28 </aop:aspect>
29 </aop:config>
30</beans>
31
32
测试输出:
1
2
3
4
5 1这是一个前置通知
2增加了一个用户
3这是一个后置通知
4
5
这种方法的好处就是简单,将所有的东西都放入一个普通类中了
而且一个切面下去,东西全在里面.
不过缺点就是由于是普通类,所以可能支持的操作就变少了.
5.1.3 方法三:通过注解来实现动态代理
新建一个代理类,其中标上注解
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 1import org.aspectj.lang.ProceedingJoinPoint;
2import org.aspectj.lang.Signature;
3import org.aspectj.lang.annotation.After;
4import org.aspectj.lang.annotation.Around;
5import org.aspectj.lang.annotation.Aspect;
6import org.aspectj.lang.annotation.Before;
7
8@Aspect//使用注解定义了切面
9public class AnnoPointCut {
10
11 @Before("execution(* com.admin.service.UserService.*(..))")
12 public void before(){
13 System.out.println("前置通知");
14 }
15 @After("execution(* com.admin.service.UserService.*(..))")
16 public void after(){
17 System.out.println("后置通知");
18 }
19
20 @Around("execution(* com.admin.service.UserService.*(..))")
21 public void around(ProceedingJoinPoint jp) throws Throwable {
22 System.out.println("环绕前");
23 Signature signature = jp.getSignature();
24 System.out.println("signatuer:"+signature);
25
26 Object proceed = jp.proceed();
27 System.out.println("环绕后");
28 }
29}
30
31
xml中需要添加支持:
输出结果:
1
2
3
4
5
6
7
8 1环绕前
2signatuer:void com.admin.service.UserService.add()
3前置通知
4增加了一个用户
5环绕后
6后置通知
7
8
其实注解操作只需要几个简单的点就好了:
-
@Aspect:定义该类为切面类.
-
@Before:定义该方法是前置通知
-
@After:定义该方法为后置通知.
-
@Around:定义该方法为环绕通知.
-
其中根据ProceedingJoinPoint.proceed()来确定环绕前后.
- 需要抛出异常.
还有其他方法都是一样的.
注意一下环绕通知被前置和后置包括在里面.
和直接使用JDK来实现动态代理相比,其实用spring的AOP的话还是很简单的.
6.整合Mybatis
首先需要导包:
- Mybatis的所有包:MySql,mybatis
- 导入spring的所有包:spring-jdbc,spring-webmvc,spring-test,aspectjweaver
- 导入mybatis和spring整合包:mybatis-spring
- 导入单元测试和lombok方便:junit,lombok
6.1 Mybatis-Spring简介
Mybatis-Spring有官方文档的:http://mybatis.org/spring/zh/index.html
6.1.1 什么是Mybatis-Spring?
简介中简要的介绍了什么是Mybatis-Spring:
- MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中.
- 它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器mapper 和 SqlSession 并注入到 bean中
- 可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring
6.1.2 版本情况
Mybatis-Spring有两个版本,分别是2.0和1.3,注意一下使用的区别.
6.2 回顾Mybatis
导完包后,我们简单的写一个Mybatis的crud操作,然后通过这个简单的样例来修改整合到我们的Mybatis-Spring中.
首先确定接口和实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1package com.admin.dao;
2
3import com.admin.pojo.User;
4
5import java.util.List;
6
7public interface UserMapper {
8// void add();
9// void delete();
10// void update();
11 List<User> selectAll();
12}
13
14
实体类:用来limbok来简化代码
1
2
3
4
5
6
7
8
9
10 1package com.admin.pojo;
2
3import lombok.Data;
4
5@Data
6public class User {
7 private String name;
8}
9
10
然后是配置Mybatis-Config.xml和mapper.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 1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-config.dtd">
5<configuration>
6
7 <properties resource="db.properties"/>
8
9 <settings>
10 <setting name="logImpl" value="STDOUT_LOGGING"/>
11 </settings>
12
13 <typeAliases>
14 <package name="com.admin.pojo"/>
15 </typeAliases>
16
17 <environments default="development">
18 <environment id="development">
19 <transactionManager type="JDBC"/>
20 <dataSource type="POOLED">
21 <property name="driver" value="${driver}"/>
22 <property name="url" value="${url}"/>
23 <property name="username" value="${username}"/>
24 <property name="password" value="${password}"/>
25 </dataSource>
26 </environment>
27 </environments>
28
29 <mappers>
30 <mapper resource="com/admin/dao/UserMapper.xml"/>
31 </mappers>
32</configuration>
33
34
其中我用了db.properties资源文件来引入数据库的变量
1
2
3
4
5
6 1driver=com.mysql.cj.jdbc.Driver
2url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
3username=root
4password=123456
5
6
根据上面的接口,我们在resoueces中创建目录结构相同的mapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE mapper
3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
6<mapper namespace="com.admin.dao.UserMapper">
7
8 <select id="selectAll" resultType="user">
9 select * from user;
10 </select>
11
12</mapper>
13
14
写一个测试类来测试是否成功连接
先写一个工具类来简化获取SqlSession代码
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 1package com.admin.utils;
2
3import org.apache.ibatis.io.Resources;
4import org.apache.ibatis.session.SqlSession;
5import org.apache.ibatis.session.SqlSessionFactory;
6import org.apache.ibatis.session.SqlSessionFactoryBuilder;
7
8import java.io.IOException;
9import java.io.InputStream;
10
11public class MybatisUtils {
12
13 private static SqlSessionFactory factory;
14 static {
15 String resource = "mybatis-config.xml";
16 try {
17 InputStream inputStream = Resources.getResourceAsStream(resource);
18 factory = new SqlSessionFactoryBuilder().build(inputStream);
19
20 } catch (IOException e) {
21 e.printStackTrace();
22 }
23 }
24 public static SqlSession getSqlSession(){
25 return factory.openSession();
26 }
27}
28
29
然后测试类中调用测试即可.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1package com.admin;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import com.admin.utils.MybatisUtils;
6import org.apache.ibatis.session.SqlSession;
7import org.junit.Test;
8
9import java.util.List;
10
11public class MyTest {
12 @Test
13 public void test1(){
14 SqlSession sqlSession = MybatisUtils.getSqlSession();
15 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
16 List<User> userList = userMapper.selectAll();
17 for (User user : userList) {
18 System.out.println(user);
19 }
20 sqlSession.close();
21 }
22}
23
24
输出结果:由于我们配置的有简单的日志,因此可以看到获取数据的过程.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 1Checking to see if class com.admin.pojo.User matches criteria [is assignable to Object]
2PooledDataSource forcefully closed/removed all connections.
3PooledDataSource forcefully closed/removed all connections.
4PooledDataSource forcefully closed/removed all connections.
5PooledDataSource forcefully closed/removed all connections.
6Opening JDBC Connection
7Created connection 1906879951.
8Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
9==> Preparing: select * from user;
10==> Parameters:
11<== Columns: id, name
12<== Row: 1, a
13<== Row: 2, b
14<== Row: 3, c
15<== Total: 3
16User(name=a)
17User(name=b)
18User(name=c)
19Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
20Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
21Returned connection 1906879951 to pool.
22
23
Nice~下面我们就参考官方的入门来改造一下这个Mybatis.
6.3 快速上手
按官方的来.
首先要再Spring中配置一个SqlSessionFactory和至少一个数据映射器类
我们可以直接使用SqlSessionFactoryBean来创建SqlSessionFactory不过需要先配置好数据源dataSource.
顺便创建出我们的sqlSession.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
2 <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
3 <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
4 <property name="username" value="root"/>
5 <property name="password" value="123456"/>
6</bean>
7
8<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
9 <property name="dataSource" ref="dataSource"/>
10 <!-- 绑定mybatis配置文件 -->
11 <property name="configLocation" value="classpath:mybatis-config.xml"/>
12 <!-- 配置映射器mapper 当然也可以在mybatis-config中配置 -->
13 <property name="mapperLocations" value="classpath:com/admin/dao/UserMapper.xml"/>
14</bean>
15
16<!--SqlSessionTemplate 就是我们使用的sqlSession-->
17<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
18 <constructor-arg index="0" ref="sqlSessionFactory"/>
19</bean>
20
21
此时,我们已经不需要工具类了,因为我们已经可以获得SqlSession了.就可以删掉获得SqlSession的工具类了.
创建UserMapperImpl实现类
由于Spring中所有东西都在容器中被托管.为了我们能够直接调用接口中的方法,我们需要一个实现类来实现它
直接调用sqlSession中的getMapper方法来通过Mybatis的Mapper.xml来实现具体的功能,我们要做的就是把功能绑定起来罢了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1package com.admin.dao.Impl;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import org.mybatis.spring.SqlSessionTemplate;
6
7import java.util.List;
8
9public class UserMapperImpl implements UserMapper {
10 private SqlSessionTemplate sqlSession;
11
12 public void setSqlSession(SqlSessionTemplate sqlSession) {
13 this.sqlSession = sqlSession;
14 }
15
16 public List<User> selectAll() {
17 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
18 return mapper.selectAll();
19 }
20}
21
22
将这个Impl实现类注册到bean中
1
2
3
4
5 1<bean id="userMapper" class="com.admin.dao.Impl.UserMapperImpl">
2 <property name="sqlSession" ref="sqlSession"/>
3</bean>
4
5
测试类中通过Spring来调用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1package com.admin;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import org.junit.Test;
6import org.springframework.context.ApplicationContext;
7import org.springframework.context.support.ClassPathXmlApplicationContext;
8
9public class MyTest {
10 @Test
11 public void test1(){
12 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
13 UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
14 for (User user : userMapper.selectAll()) {
15 System.out.println(user);
16 }
17 }
18}
19
20
输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7642df8f] will not be managed by Spring
2==> Preparing: select * from user;
3==> Parameters:
4<== Columns: id, name
5<== Row: 1, a
6<== Row: 2, b
7<== Row: 3, c
8<== Total: 3
9Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@791d1f8b]
10User(name=a)
11User(name=b)
12User(name=c)
13
14
可以看出来,改造还是挺成功的,我们进行了以下的修改
- 新加入一个applicationContext.xml的Spring配置类
- mybatis-config.xml中的数据源,mappers删掉了.
- 获取SqlSession的工具类删掉了.
- 新增了一个接口的实现类
6.4 快速小结
但是我们调用的时候就不再需要sqlSession这些东西了,spring都在后面帮我们做好了,我们显式的使用方法只需要通过IoC容器就好了.
大家可能此处有些小疑问:为什么要多写一个impl呢,我们在测试类中一样可以用sqlsession来获取mapper来使用方法啊.
(其实这就是我当时想到的一个小问题,不过这东西稍微想一下就清楚了)
我们来看一下如果不写impl会表现出什么效果:
我们也不需要多改什么,只是不用impl罢了,修改以下测试类,使用sqlSession来获取方法.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1public class MyTest {
2 @Test
3 public void test1(){
4 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
5 //UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
6 SqlSessionTemplate sqlSession = context.getBean("sqlSession",SqlSessionTemplate.class);
7 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
8 for (User user : userMapper.selectAll()) {
9 System.out.println(user);
10 }
11 }
12}
13
14
和之前的比较以下,是不是多了很多东西,我们在测试类中还要调用dao层的东西.这非常不利于我们的开发.
如果将他们分开,dao专注dao的事情,测试类只用测试就好了,其他层各司其职.可以更加简化.而且impl中也没有多写什么,只是把测试类中的代码放到了impl里面,我们以后测试类中就不用重复写了.这也方便了很多.
总之,尽量减少耦合.
6.5 通过继承SqlSessionDaoSupport来实现impl
使用SqlSession这一个标签中提到了另一种使用方法:http://mybatis.org/spring/zh/sqlsession.html
直接在impl中继承一个SqlSessionDaoSupport,然后就可以直接通过getSqlSession()方法获得SqlSessionTemplate了.然后就是正常的方法了.
这个类中不需要显式的注入SqlSession.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1package com.admin.dao.Impl;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import org.mybatis.spring.support.SqlSessionDaoSupport;
6
7import java.util.List;
8
9public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
10 public List<User> selectAll() {
11 return getSqlSession().getMapper(UserMapper.class).selectAll();
12 }
13}
14
15
16
同样写完之后需要到Ioc中去配置对应的bean.虽然这个类中没有构造和set方法,但是继承的SqlSessionDaoSupport中有一个set方法.
通过这个set方法我们可以知道,传入Factory和Template都是可以的.
1
2
3
4
5 1<bean id="userMapper2" class="com.admin.dao.Impl.UserMapperImpl2">
2 <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
3</bean>
4
5
在测试类中测试效果相同
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 1package com.admin;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import org.junit.Test;
6import org.springframework.context.ApplicationContext;
7import org.springframework.context.support.ClassPathXmlApplicationContext;
8
9
10public class MyTest {
11 @Test
12 public void test1(){
13 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
14 UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
15 for (User user : userMapper.selectAll()) {
16 System.out.println(user);
17 }
18 }
19}
20/*
21JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@68be8808] will not be managed by Spring
22==> Preparing: select * from user;
23==> Parameters:
24<== Columns: id, name
25<== Row: 1, a
26<== Row: 2, b
27<== Row: 3, c
28<== Total: 3
29Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3336e6b6]
30User(name=a)
31User(name=b)
32User(name=c)
33*/
34
35
7.声明式事务
7.1 回顾事务
事务是什么:
- 把一组业务当成一个业务来做,要么都成功要么都失败
- 事务在项目中非常重要,涉及到数据的一致性问题!非常重要!
- 确保完整性和一致性.
事务的ACID原则
- 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态.
- **一致性(Consistency)?*事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定.
- 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离.
- **持久性(Durability)?*当事务正确完成后,它对于数据的改变是永久性的.无论发生什么都不会再改变了
7.2 Spring中的事务管理
7.2.1 声明式事务
spring给我们已经整理好了所有的操作.我们只需要再xml中配置就行了.
使用Spring的事务管理,直接调用.我们不需要编写事务操作.
7.2.1.1 方法一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1<!--配置声明式事务-->
2<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
3 <constructor-arg ref="dataSource" />
4</bean>
5<!--结合AOP实现事务织入-->
6<!--配置事务通知-->
7<tx:advice id="txAdvice" transaction-manager="transactionManager">
8 <!--给那些方法配置事务配置事务的-->
9 <tx:attributes>
10 <tx:method name="add"/>
11 <tx:method name="delete"/>
12 <tx:method name="update" propagation="REQUIRED"/>
13 <tx:method name="query" read-only="true"/>
14 <tx:method name="*"/>
15 </tx:attributes>
16</tx:advice>
17
18<!--配置事务的切入-->
19<aop:config>
20 <aop:pointcut id="txPointCut" expression="execution(* com.admin.dao.*.*(..))"/>
21 <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
22</aop:config>
23
24
修改以下方法测试一下:
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 1package com.admin.dao.Impl;
2
3import com.admin.dao.UserMapper;
4import com.admin.pojo.User;
5import org.apache.ibatis.session.SqlSession;
6import org.mybatis.spring.support.SqlSessionDaoSupport;
7
8import java.util.List;
9
10public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
11 public List<User> selectAll() {
12 UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
13 userMapper.deleteUserById(4);
14 userMapper.addUser(new User(5,"e"));
15
16 return getSqlSession().getMapper(UserMapper.class).selectAll();
17 }
18
19 public void addUser(User user) {
20 getSqlSession().getMapper(UserMapper.class).addUser(user);
21 }
22
23 public void deleteUserById(int id) {
24 getSqlSession().getMapper(UserMapper.class).deleteUserById(id);
25 }
26}
27
28
修改一下再看看.发现已经被事务管理了.
7.2.1.2 方法二:
而官方的操作是另一种方法,通过直接配置在方法中.
创建DataSourceTransactionManager对象.
使用Spring的事务名命空间或者JtaTransactionManagerFactoryBean.
使用一个子类或者由容器指定一个子类作为事务管理器
在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个 SqlSession。如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。
注意,如果你想使用由容器管理的事务,而不想使用 Spring 的事务管理,你就不能配置任何的 Spring 事务管理器。并必须配置 SqlSessionFactoryBean 以使用基本的 MyBatis 的 ManagedTransactionFactory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
2 <constructor-arg ref="dataSource" />
3</bean>
4
5<tx:jta-transaction-manager />
6
7<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
8 <property name="dataSource" ref="dataSource" />
9 <property name="transactionFactory">
10 <bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
11 </property>
12</bean>
13
14
7.2.2 编程式事务
用的似乎不多,多少抄一下把.留个印象.
MyBatis 的 SqlSession 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession 或映射器。也就是说,Spring 总是为你处理了事务。
你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit(),SqlSession.rollback() 或 SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的映射器时,这些方法也不会暴露出来。
无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
如果你想编程式地控制事务,请参考 the Spring reference document(Data Access -Programmatic transaction management-)。下面的代码展示了如何使用 PlatformTransactionManager 手工管理事务。
1
2
3
4
5
6
7
8
9
10
11 1TransactionStatus txStatus =
2 transactionManager.getTransaction(new DefaultTransactionDefinition());
3try {
4 userMapper.insertUser(user);
5} catch (Exception e) {
6 transactionManager.rollback(txStatus);
7 throw e;
8}
9transactionManager.commit(txStatus);
10
11
在使用 TransactionTemplate 的时候,可以省略对 commit 和 rollback 方法的调用。
1
2
3
4
5
6
7 1TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
2transactionTemplate.execute(txStatus -> {
3 userMapper.insertUser(user);
4 return null;
5});
6
7
注意:虽然这段代码使用的是一个映射器,但换成 SqlSession 也是可以工作的。