初识SSM框架

SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的内容),常作为数据源较简单的web项目的框架。

SSM

页面发送请求给控制器,控制器调用业务层处理逻辑,逻辑层向持久层发送请求,持久层与数据库交互,后将结果返回给业务层,业务层将处理逻辑发送给控制器,控制器再调用视图展现数据。

1.Spring:Spring像是整个项目中装配bean的工厂,在配置文件中可以指定使用特定的参数去调用实体类的构造方法来实例化对象。Spring的核心思想是IoC(控制反转),即不再需要程序员去显式地new一个对象,而是让Spring框架完成这一切。
2.SpringMVC:SpringMVC在项目中拦截用户请求,它的核心Servlet即DispatcherServlet承担中介或是前台的职责,将用户请求通过HandlerMapping去匹配Controller,Controller就是具体对应请求所执行的操作。SpringMVC相当于SSH框架中struts。
3.MyBatis:MyBatis是对jdbc的封装,它让数据库底层操作变的透明。mybatis的操作都是围绕一个sqlSessionFactory实例展开的。mybatis通过配置文件关联到各实体类的Mapper文件,Mapper文件中配置了每个类对数据库所需进行的sql语句映射。在每次与数据库交互时,通过sqlSessionFactory拿到一个sqlSession,再执行sql命令。

1.持久层(Data Access Layer 数据访问层 Dao层):建立实体类和数据库表映射(ORM映射),完成对象数据和关系数据的转换。

2.业务层(Business Logic Layer 逻辑层 service层):将业务中操作封装成方法,同时保证方法中所有的数据库更新操作,即保证同时成功或同时失败。

3.表现层(UI层 视图层 界面层 Controller层):MVC(Model-View-Controler)模式 负责请求转发,接受页面过来的参数,传给Service处理,接受返回值传给页面。


MyBatis

1.MyBatis是一个Java持久层框架,它通过XML描述符或注解把对象与存储过程或SQL语句关联起来,映射成数据库内对应的纪录。MyBatis是在Apache许可证 2.0下分发的自由软件,是iBATIS 3.0的分支版本,其维护团队也包含iBATIS的初创成员。(2013年迁移至Github)

2.与其他对象关系映射框架不同,MyBatis没有将Java对象与数据库表关联起来,而是将Java方法与SQL语句关联。MyBatis允许用户充分利用数据库的各种功能,例如存储过程、视图、各种复杂的查询以及某数据库的专有特性。

3.与JDBC相比,MyBatis简化了相关代码:SQL语句在一行代码中就能执行。MyBatis提供了一个映射引擎,声明式的把SQL语句执行结果与对象树映射起来。通过使用一种内建的类XML表达式语言,或者使用Apache Velocity集成的插件,SQL语句可以被动态的生成。

4.MyBatis与Spring Framework和Google Guice集成,这使开发者免于依赖性问题。

5.MyBatis支持声明式数据缓存(declarative data caching)。当一条SQL语句被标记为”可缓存”后,首次执行它时从数据库获取的所有数据会被存储在一段高速缓存中,今后执行这条语句时就会从高速缓存中读取结果,而不是再次命中数据库。MyBatis提供了基于 Java HashMap 的默认缓存实现,以及用于与OSCache、Ehcache、Hazelcast和Memcached连接的默认连接器。MyBatis还提供API供其他缓存实现使用。

简介

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
  • MyBatis是一款优秀的持久层框架。
  • 支持自定义 SQL、存储过程以及高级映射。
  • MyBatis免除了几乎所有的JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
  • 优点
    • 简单易学
    • 灵活:sql写在xml里,便于统一管理和优化
    • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

持久层

  • J2EE的三层结构是指表示层(Presentation),业务逻辑层(Business Logic)以及基础架构层(Infrastructure)。
  • 实际的项目通常对三层结构进行扩展来满足项目的具体要求,最常用是将三层体系扩展为五层体系,即表示层(Presentation)、控制/中介层(Controller/Mediator)、领域层(Domain)、数据持久层(Data Persistence)和数据源层(Data Source)。它其实是在三层架构中增加了两个中间层。控制/中介层位于表示层和领域层之间。
  • 持久化:将程序数据在持久状态和瞬时状态间转换的机制。即瞬时数据(如内存中的数据,不能永久保存)持久化为持久数据(如持久化至数据库中,能长久保存)。

前期配置

1.数据库准备

1
2
3
4
5
6
7
8
9
10
11
12
USE `mybatis`;

CREATE TABLE `user` (
`id` INT (20) NOT NULL PRIMARY KEY,
`name` VARCHAR (30) DEFAULT NULL,
`pwd` VARCHAR (30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'Tim','12121'),
(2,'Tom','1Yud345'),
(3,'Kate','true456')

2.依赖导入

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
<!--导入依赖-->
<dependencies>
<!--MySQL驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>

<!--Mybatis-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!--junit-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

</dependencies>

3.在resources文件夹下编写mybatis-config.xml(MyBatis配置文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="0513"/>
</dataSource>
</environment>
</environments>

</configuration>

初识Mybatis

构建SqlSessionFactory

1.每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。而 SqlSessionFactoryBuilder则可以从XML配置文件或一个预先配置的Configuration实例来构建出SqlSessionFactory实例。

2.从XML文件中构建 SqlSessionFactory的实例非常简单,建议使用类路径下的资源文件进行配置。但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file://URL构造的输入流。MyBatis包含一个名叫Resources的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

1
2
3
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>

XML头部的声明用来验证XML文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的XML映射文件包含了SQL代码和映射定义信息。


获取SqlSession

有了SqlSessionFactory,即可从中获得SqlSession的实例。SqlSession提供了在数据库执行SQL命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的SQL语句。例如:

1
2
3
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),这样代码不仅更清晰,更加类型安全,不用担心可能出错的字符串字面值以及强制类型转换。

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}

编写Mapper

一个语句既可以通过XML定义,也可以通过注解定义。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>

在一个XML映射文件中,可以定义无数个映射语句,这样一来,XML头部和文档类型声明部分就显得微不足道了。在命名空间org.mybatis.example.BlogMapper中定义了一个名为selectBlog的映射语句,这样你就可以用全限定名org.mybatis.example.BlogMapper.selectBlog来调用映射语句了,就像上面例子中那样:

1
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

这种方式和用全限定名调用Java象的方法类似。这样,该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的select 语句匹配到对应名称、参数和返回类型的方法。

1
2
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

第二种方法有很多优势,首先它不依赖于字符串字面值,会更安全一点;其次,如果你的 java IDE有代码补全功能,那么代码补全可以帮你快速选择到映射好的SQL语句。


命名空间 Namespaces

1.必须指定命名空间,命名空间的作用有两个:

  • 用更长的全限定名来将不同的语句隔离开来
  • 实现了接口绑定

只要将命名空间置于合适的Java包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。


命名解析

为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则:

  • 全限定名(如com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用
  • 短名称(如selectAllThings
    • 如果全局唯一也可以作为一个单独的引用。
    • 如果不唯一,有两个或两个以上的相同名称(如com.foo.selectAllThingscom.bar.selectAllThings),那么使用时就会产生短名称不唯一的错误,这种情况下就必须使用全限定名。

对于像BlogMapper这样的映射器类来说,还有另一种方法来完成语句映射。它们映射的语句可以不用XML来配置,而可以使用Java注解来配置。

1
2
3
4
5
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java注解不仅力不从心,还会让你本就复杂的SQL语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用XML来映射语句。


作用域Scope和生命周期

1.不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

2.对象生命周期和依赖注入框架:依赖注入框架可以创建线程安全的、基于事务的SqlSession和映射器,并将它们直接注入到你的bean中,因此可以直接忽略它们的生命周期。


1.SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了。因此SqlSessionFactoryBuilder实例的最佳作用域是方法作用域(即局部方法变量)。

  • 可以重用SqlSessionFactoryBuilder来创建多个SqlSessionFactory实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

2.SqlSessionFactory

  • SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例

  • 使用SqlSessionFactory的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory被视为一种代码坏习惯。因此SqlSessionFactory的最佳作用域是应用作用域。(最简单的就是使用单例模式或者静态单例模式)

3.SqlSession

  • 每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

  • 绝对不能将SqlSession实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession实例的引用放在任何类型的托管作用域中,比如Servlet框架中的 HttpSession。

  • 如果你现在正在使用一种Web框架,考虑将SqlSession放在一个和HTTP请求相似的作用域中。换句话说,每次收到HTTP请求,就可以打开一个SqlSession,返回一个响应后,就关闭它。

  • 关闭操作很重要,为了确保每次都能执行关闭操作,应该把这个关闭操作放到finally块中

1
2
3
4
5
/*确保 SqlSession 关闭的标准模式*/
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
/*在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭*/

映射器实例

  • 映射器是一些绑定映射语句的接口。

  • 映射器接口的实例是从SqlSession中获得的。

  • 虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的SqlSession相同。但方法作用域才是映射器实例的最合适的作用域,即映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。最好将映射器放在方法作用域内。

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}

1.编写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
package com.learn.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;


import java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {
public static SqlSessionFactory sqlSessionFactory;
static {
try {
/*使用Mybatis获取SqlSessionFactory对象*/
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}

2.编写实体类

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
package com.learn.pojo;

public class User {

private int id;
private String name;
private String pwd;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", pwd=" + pwd + "]";
}
}

3.编写接口UserDao

1
2
3
4
5
6
7
8
package com.learn.dao;

import com.learn.pojo.User;
import java.util.List;

public interface UserDao {
List<User> getUserList();
}

4.接口实现类由原来的UserDaoImpl转变为Mapper配置xml文件

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.learn.dao.UserDao">

<select id="getUserList" resultType="com.learn.pojo.User">
select * from mybatis.user
</select>
</mapper>
1
2
3
4
5
<!--mybatis-config.xml-->
<!--每个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/learn/dao/UserMapper.xml"/>
</mappers>

1.资源无法导出找不到Mapper.xml文件(target下无此文件)

  • 将Mapper.xml移至resources目录下(Maven下其他目录资源文件无法导出到target)
  • 在pom.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
<!--父工程pom.xml导入依赖-->
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

<groupId>com.noob</groupId>
<artifactId>MybatisLearning</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>Mybatis01</module>
</modules>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

</dependencies>
</project>
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
<!--pom.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--父工程-->
<parent>
<artifactId>MybatisLearning</artifactId>
<groupId>com.noob</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>Mybatis01</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

</project>

1.Mapper写中文注释,会导致Java.lang.ExceptionInInitializerError,修改Mapper配置文件:

<?xml version="1.0" encoding="UTF-8" ?>改为<?xml version="1.0" encoding="UTF8" ?>即可。

5.在test文件下新建与java文件相同的文件结构进行测试

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
package com.learn.dao;

import com.learn.pojo.User;
import com.learn.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {
@Test
public void test(){
/*获取SqlSession对象*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
/*执行SQL*/
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();

for(User user : userList){
System.out.println(user.toString());
}
/*关闭SqlSession对象*/
sqlSession.close();
}
}

CRUD

1.namespace:namespace的包名要与Dao/Mapper接口的包名一致。

2.select:查询

  • id:对应namespace中的方法名
  • parameterType:传入参数(id对应定义方法的参数类型)
    • 基本数据类型:int、string、long、Date #{参数}获取
    • 复杂数据类型:类(JavaBean、Integer等)和Map #{属性名}可直接获取
  • resultType:sql语句执行的返回值(id对应定义方法的返回值类型)

3.insert:插入

4.delete:删除

5.update:更新

6.#{参数名}:以预编译的形式,将参数(接口实现方法传递)设置到SQL语句中(PreparedStatement)。

  • 只有一个参数值,可使用任意名#{anyname}取出参数值
  • 多个参数使用Map,使用#{key}取出对应参数值

1.编写接口 -> 2.编写对应的mapper中的sql语句 -> 3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.learn.dao;

import com.learn.pojo.User;
import java.util.List;

public interface UserDao {
List<User> getUserList();

User getUserByID(int id);

int deleteUserByID(int id);

int insertUser(User user);

int updateUserByID(User user);

}
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.dao.UserDao">

<select id="getUserList" resultType="com.learn.pojo.User">
select * from mybatis.user
</select>

<select id="getUserByID" parameterType="int" resultType="com.learn.pojo.User">
select * from mybatis.user where id=#{id}
</select>

<delete id="deleteUserByID" parameterType="int" >
delete from mybatis.user where id=#{id}
</delete>

<insert id="insertUser" parameterType="com.learn.pojo.User" keyProperty="id">
insert into User(id,name,pwd) values(#{id},#{name},#{pwd})
</insert>

<update id="updateUserByID" parameterType="com.learn.pojo.User">
update mybatis.user
<set>
<if test="name!=null">name=#{name},</if>
<if test="pwd!=null">pwd=#{pwd}</if>
</set>
where id = #{id}
<!-- update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id} -->
</update>
</mapper>
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
package com.learn.dao;

import com.learn.pojo.User;
import com.learn.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {
@Test
public void test(){
/*获取SqlSession对象*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
/*执行SQL*/

/*
方式1:getMapper
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
*/

/*方式2*/
List<User> userList = sqlSession.selectList("com.learn.dao.UserDao.getUserList");



for(User user : userList){
System.out.println(user.toString());
}
/*关闭SqlSession对象*/
sqlSession.close();
}

@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUserByID(4);
System.out.println(user.getName());
sqlSession.close();
}

@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
int n = userDao.deleteUserByID(4);
System.out.println("已修改结果条数:"+n);
/*增删改需要进行事务提交*/
sqlSession.commit();
sqlSession.close();

}

@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = new User(4,"Tr","Kikilol");
int n = userDao.insertUser(user);
System.out.println("已修改结果条数:"+n);
sqlSession.commit();
sqlSession.close();

}
@Test
public void test5(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = new User(6,"pme","TryAgain");
int n = userDao.updateUserByID(user);
System.out.println("已修改结果条数:"+n);
sqlSession.commit();
sqlSession.close();

}
}

Map

适用与实体类或者数据库中的表字段过多,考虑使用Map(sql取出key)

  • 只有一个基本类型参数时,可直接在sql中取得,无需声明 parameterType
  • 多个参数时,使用Map或者注解
1
2
3
4
5
6
7
8
/*UserMapper.java*/
public interface UserMapper {
List<User> getUserList();

int addUser(Map<String,Object> map);

User getUserByID(Map<String,Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--/*UserMapper.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.dao.UserMapper">
<select id="getUserList" resultType="com.learn.pojo.User">
select * from mybatis.user
</select>

<select id="getUserByID" resultType="com.learn.pojo.User" parameterType="Map">
select * from mybatis.user where id = #{userId} and name = #{userName}
</select>

<insert id="addUser" parameterType="map">
insert into mybatis.user (id,name,pwd) values (#{userid},#{username},#{password})
</insert>
</mapper>
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
public class UserMapperTest {
@Test
public void test(){

SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

for(User user : userList){
System.out.println(user.toString());
}
/*关闭SqlSession对象*/
sqlSession.close();
}

@Test
public void testMapInsert(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/*接口 对象 = new 实现类*/
Map<String,Object> map = new HashMap<String,Object>();
map.put("userid",8);
map.put("username","Kid");
map.put("password","56565");
int i =mapper.addUser(map);
System.out.println("修改结果条数:"+i);
sqlSession.commit();
sqlSession.close();
}

@Test
public void testMapSelect(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("userId",4);
map.put("userName","Tr");
User user =mapper.getUserByID(map);
System.out.println(user.toString());
sqlSession.close();
}
}

模糊查询

1
List<User> getUserList(String value);

1.在Mapper中进行处理

1
2
3
<select id="getUserList" resultType="com.learn.pojo.User">
select * from mybatis.user where name like concat("%",#{value},"%")
</select>
1
List<User> userList = mapper.getUserList("T");

2.在调用方法时使用通配符% %

1
2
3
<select id="getUserList" resultType="com.learn.pojo.User">
select * from mybatis.user where name like #{value}
</select>
1
List<User> userList = mapper.getUserList("%T%");

XML配置

mybatis-config.xml

MyBatis的配置文件(元素顺序从上到下否则会报错)包含了影响MyBatis行为的设置和属性信息。配置文档的顶层结构如下:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
  • databaseIdProvider(数据库厂商标识)
  • mappers(映射器)

properties

属性(properties)可以在外部进行配置,并可以进行动态替换。既可以在Java属性文件中配置这些属性,也可以在properties元素的子元素中设置。

1
2
3
4
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。

1
2
3
4
5
6
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>

该例中username和password将会由properties元素中设置的相应值来替换。 driver和url属性将会由 config.properties文件中对应的值来替换,配置更加灵活。


可以在SqlSessionFactoryBuilder.build()方法中传入属性值。例如:

1
2
3
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

如果一个属性在不只一个地方进行了配置,MyBatis将按照下面的顺序来加载:

1.首先读取在properties元素体内指定的属性。
2.然后根据properties元素中的resource属性读取类路径下属性文件,或根据url属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
3.最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

即通过方法参数传递的属性具有最高优先级,resource/url属性中指定的配置文件次之,最低优先级的则是properties元素中指定的属性。


从MyBatis3.4.2开始,可以为占位符指定一个默认值。

1
2
3
4
5
<dataSource type="POOLED">
<!-- ... -->
<property name="username" value="${username:ut_user}"/>
<!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
</dataSource>

这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。

1
2
3
4
5
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
<!-- 启用默认值特性 -->
</properties>

如果你在属性名中使用了:字符(如:db:username),或者在SQL映射中使用了OGNL表达式的三元运算符(如${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符。

1
2
3
4
5
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
<!-- 修改默认值的分隔符 -->
</properties>
1
2
3
4
<dataSource type="POOLED">
<!-- ... -->
<property name="username" value="${db:username?:ut_user}"/>
</dataSource>

typeAliases

typeAliases(类型别名)可为Java类型设置一个缩写名字。 它仅用于XML配置,意在降低冗余的全限定类名书写。

1
2
3
4
5
6
7
8
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog可以用在任何使用domain.blog.Blog的地方。


也可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean。

1
2
3
<typeAliases>
<package name="domain.blog"/>
</typeAliases>

每一个在包domain.blog中的Java Bean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名来作为它的别名。比如domain.blog.Author的别名为author;若有注解,则别名为其注解值。

1
2
3
4
@Alias("author")
public class Author {
...
}

下面一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

environments

MyBatis可以配置成适应多种环境,这种机制有助于将SQL映射应用于多种数据库之中,如开发、测试和生产环境需要有不同的配置或者想在具有相同 Schema 的多个生产数据库中使用相同的SQL映射等。


尽管可以配置多个环境,但每个SqlSessionFactory 实例只能选择一种环境。(如果想连接两个数据库,就需要创建两个SqlSessionFactory实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推。即每个数据库对应一个 SqlSessionFactory 实例)

为了指定创建哪种环境,只要将它作为可选的参数传递给SqlSessionFactoryBuilder即可。可以接受环境配置的两个方法签名是:

1
2
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果忽略了环境参数,那么将会加载默认环境。

1
2
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

environments元素定义了如何配置环境。

  • 默认使用的环境ID(如default="development"
  • 每个environment元素定义的环境 ID(如id="development"
  • 事务管理器的配置(如type="JDBC"
  • 数据源的配置(如type="POOLED"

环境可以随意命名,但务必保证默认(使用)的环境ID要匹配其中一个环境ID

1
2
3
4
5
6
7
8
9
10
11
12
13
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

transactionManager

在MyBatis中有两种类型的事务管理器(type="[JDBC|MANAGED]"

  • JDBC:直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED :从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE 应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为false来阻止默认的关闭行为。
1
2
3
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>

如果你正在使用Spring + MyBatis,则没有必要配置事务管理器,因为Spring模块会使用自带的管理器来覆盖前面的配置。


dataSource

1.dataSource(数据源)元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。(虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。)

2.有三种内建的数据源类型(即type="[UNPOOLED|POOLED|JNDI]"):

  • UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED类型的数据源仅仅需要配置以下5种属性:

    • driver:JDBC驱动的Java类全限定名(并不是JDBC驱动中可能包含的数据源类)
    • url:这是数据库的JDBC URL地址
    • username:登录数据库的用户名
    • password:登录数据库的密码
    • defaultTransactionIsolationLevel:默认的连接事务隔离级别
    • defaultNetworkTimeout:等待数据库操作完成的默认网络超时时间(单位:毫秒)

    作为可选项,也可以传递属性给数据库驱动。只需在属性名加上driver.前缀即可:

    • driver.encoding=UTF8

    通过DriverManager.getConnection(url, driverProperties)方法传递值为UTF8的encoding 属性给数据库驱动。

  • POOLED:实现利用的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发Web应用快速响应请求。

    除了UNPOOLED下的属性外,还有更多属性用来配置POOLED的数据源:

    • poolMaximumActiveConnections:在任意时间可存在的活动(正在使用)连接数量,默认值10
    • poolMaximumIdleConnections:任意时间可能存在的空闲连接数。
    • poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000毫秒(即20秒)
    • poolTimeToWait:这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值20000毫秒(即20秒)
    • poolMaximumLocalBadConnectionTolerance:这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。默认值:3
    • poolPingQuery:发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是NO PING QUERY SET,这会导致多数数据库驱动出错时返回恰当的错误消息
    • poolPingEnabled:是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的SQL 语句(最好是一个速度非常快的SQL语句),默认值:false
    • poolPingConnectionsNotUsedFor:配置 poolPingQuery 的频率可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled为true时适用)
  • JNDI:数据源实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。这种数据源配置只需要两个属性:

    • initial_context:这个属性用来在 InitialContext 中寻找上下文(即initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
    • data_source:这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

    和其他数据源配置类似,可以通过添加前缀env.直接把属性传递给InitialContext。\

    • env.encoding=UTF8

    这就会在InitialContext实例化时往它的构造方法传递值为UTF8的encoding属性。


可以通过实现接口org.apache.ibatis.datasource.DataSourceFactory来使用第三方数据源实现:

1
2
3
4
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory可被用作父类来构建新的数据源适配器,

1
2
3
4
5
6
7
8
9
10
/*插入 C3P0 数据源*/
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
}

为了令其工作,在配置文件中为每个希望MyBatis调用的setter方法增加对应的属性。

1
2
3
4
5
6
7
<!--连接至 PostgreSQL 数据库-->
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>

settings

MyBatis中极为重要的调整设置,它们会改变 MyBatis的运行时行为。

常用的设置:

  • cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。(有效值 true|false 默认true)
  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。(有效值 true|false 默认false)
  • logImpl:指定MyBatis所用日志的具体实现,未指定时将自动查找。(有效值 SLF4J|LOG4J |LOG4J2 |JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING 默认值未设置)

一个配置完整的settings元素的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

example

1
2
3
4
<!--UserMapper.xml-->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
1
2
3
4
5
<!--mybatis-cofig.xml-->
<!--实体类起别名-->
<typeAliases>
<typeAlias type="com.noob.pojo.User" alias="User"/>
</typeAliases>

1
2
3
4
5
6
<!--mybatis-cofig.xml-->
<!--扫描实体类的包,默认别名就为该类的类名,首字母建议小写(大写也可)-->
<!--适用于实体类较多的情况-->
<typeAliases>
<package name="com.noob.pojo"/>
</typeAliases>
1
2
3
4
<!--UserMapper.xml-->
<select id="getUserList" resultType="user">
select * from mybatis.user
</select>

1
2
3
4
5
/*使用注解,前提也要在mybatis-cofig.xml指定包*/
@Alias("UserTest")
public class User {

}
1
2
3
<select id="getUserList" resultType="UserTest">
select * from mybatis.user
</select>

1.编写配置文件 resource-db.properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username=root
password=0513

1.Malformed database URL, failed to parse the connection string错误:

配置url应该使用&符号,不再需要amp;

2.在核心配置文件中导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<configuration>
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!--每个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/noob/dao/UserMapper.xml"/>
</mappers>
</configuration>
1
2
3
4
5
<properties resource="db.properties">
<property name="username" value="root"/>
<!--由于编写了外部文件,property元素配置优先级最低,所以密码被覆盖未造成影响-->
<property name="password" value="1111"/>
</properties>

plugins

  • mybatis-generator-core
  • mybatis-plus
  • 通用mapper

mapper

定义SQL映射语句首先需要告诉MyBatis到哪里去找到这些语句。最好的办法是直接告诉MyBatis到哪里去找映射文件。可以使用相对于类路径的资源引用,或完全限定资源定位符(包括file:///形式的URL),或类名和包名等。

1
2
3
4
5
6
7
<!-- 使用相对于类路径的资源引用 -->
<!--mapper映射器-->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
1
2
3
4
5
6
7
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!--接口和其Mapper配置文件必须同名,且在一个包下-->
1
2
3
4
5
6
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
1
2
3
4
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

1
2
3
4
5
<mappers>
<!--<mapper resource="com/noob/dao/UserMapper.xml"/>-->
<!--<mapper class="com.noob.dao.UserMapper"/>-->
<package name="com.noob.dao"/>
</mappers>

resultMap

resultMap元素是MyBatis中最重要最强大的元素。它可以让你从90%的JDBC ResultSets数据提取代码中解放出来,并在一些情形下允许你进行一些JDBC不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份resultMap能够代替实现同等功能的数千行代码。ResultMap的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

1
2
3
4
5
6
7
<!--将所有的列映射到HashMap 的键上,这由resultType属性指定-->
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>

程序更可能会使用JavaBean或POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。看看下面这个 JavaBean:

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
/*
1.基于JavaBean的规范,上面这个类有3个属性:id,username 和 hashedPassword。这些属性会对应到 select 语句中的列名
2.这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单
*/
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
1
2
3
4
5
6
7
8
9
<!-- mybatis-config.xml 中使用类型别名,避免类的全限定名-->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>

在这些情况下,MyBatis会在幕后自动创建一个ResultMap,再根据属性名来映射列到JavaBean的属性上。

如果列名和属性名不能匹配上,可以在SELECT语句中设置列别名来完成匹配,

1
2
3
4
5
6
7
8
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>

ResultMap 的优秀之处——你完全可以不用显式地配置它们。当然也可以显式使用外部的resultMap,是解决列名不匹配的另外一种方式。

1
2
3
4
5
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>

然后在引用它的语句中设置resultMap属性即可(去掉了resultType属性)

1
2
3
4
5
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>

example

1
2
3
4
5
6
7
8
9
10
11
12
public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByID(1);
for(User user : userList){
System.out.println(user.toString());
}
sqlSession.close();
}
}
1
2
3
4
5
6
7
public class User {

private int id;
private String name;
private String password;

}

查询结果:User{id=1, name=’Tim’, password=’null’}
原因:select id,name,pwd from mybatis.user where id = #{id},数据库字段名pwd与实体类字段名password不一致

1.别名

1
2
3
<select id="getUserByID" resultType="com.noob.pojo.User">
select id,name,pwd as password from mybatis.user where id=#{id}
</select>

2.resultMap结果集映射

1
2
3
4
5
6
7
8
9
10
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column数据库的字段,property实体类的字段-->
<result column="name" property="name"/>
<result column="id" property="id"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserByID" resultMap="UserMap">
select id,name,pwd from mybatis.user where id=#{id}
</select>
1
2
3
<typeAliases>
<typeAlias alias="User" type="com.noob.pojo.User"/>
</typeAliases>

日志

1.日志工厂

1.logImpl:指定MyBatis所用日志的具体实现,未指定时将自动查找。

2.有效值:

  • SLF4J
  • LOG4J
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING(标准日志输出)
  • NO_LOGGING
  • 标准日志工厂的实现
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

log4j

Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等

  • 可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

1.导入Log4j

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

2.log4j.properties(resource)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/noob.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3.配置Log4j为日志的实现

1
2
3
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.apache.log4j.Logger;

public class UserMapperTest {
/*日志对象,参数为当前类的class*/
static Logger logger = Logger.getLogger(UserMapper.class);
@Test
public void testLog4j(){
logger.info("info:进入testLog4j");
logger.debug("debug:进入testLog4j");
logger.error("error:进入testLog4j");
}
}

没用配置文件显示log4j警告:将pom.xml里的log4j包依赖删掉即可


分页

limit

1.定义接口

1
2
3
4
public interface UserMapper {
List<User> getUserByLimit(Map<String,Integer> map);
}

2.编写Mapper.xml

1
2
3
4
5
6
7
8
9
10
<resultMap id="UserMap" type="User">
<!--column数据库的字段,property实体类的字段-->
<result column="name" property="name"/>
<result column="id" property="id"/>
<result column="pwd" property="password"/>
</resultMap>

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>

3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",3);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for(User user:userList){
System.out.println(user.toString());
}
sqlSession.close();
}

RowBounds

1.定义接口

1
List<User> getUserByRowBounds();

2.编写Mapper.xml

1
2
3
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>

3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void getUserByRowBounds(){

SqlSession sqlSession = MybatisUtils.getSqlSession();

RowBounds rowBounds =new RowBounds(1,3);
List<User> userList =sqlSession.selectList("com.noob.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for(User user:userList){
System.out.println(user.toString());
}
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
sqlSession.close();
}

PageHelper

PageHelper

文档


注解开发

面向接口编程

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
1.关于接口的理解:

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)

    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)

  • 一个体有可能有多个抽象面

  • 抽象体与抽象面是有区别的

2.区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多体现对系统整体的架构

1.注解在接口实现

1
2
3
4
5
public interface UserMapper {
@Select("select id,name,pwd as password from mybatis.user")
List<User> getUsers();
}

2.在核心配置文件中绑定接口

1
2
3
<mappers>
<mapper class="com.noob.dao.UserMapper"/>
</mappers>

3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserMapperTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
/*本质:反射机制 底层:动态代理*/
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUsers();
for(User user : userList){
System.out.println(user.toString());
}
sqlSession.close();
}
}

Mybatis执行流程


在测试方法处进行Debug,可以初步了解Mybatis的执行流程。


注解CRUD

在工具类创建时实现自动提交事务

1
2
3
public  static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}

1.编写接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface UserMapper {
@Select("select id,name,pwd as password from mybatis.user")
List<User> getUsers();

@Select("select id,name,pwd as password from mybatis.user where id=#{id}")
User getUsersByID(int id);
/*注解开发时,方法存在多个参数时,需要@Param("sql注解参数名")注解*/
@Select("select id,name,pwd as password from mybatis.user where id=#{id} and name like #{name} ")
User getUserByIDAndName(@Param("id") int id, @Param("name") String name);

@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
int insertUserByID(User user);

@Delete("delete from user where id=#{id}")
int deleteUserByID(int id);

@Update("update user set id=#{id},name=#{name},pwd=#{password} where id = #{id}")
int updateUserByID(User user);


}

2.在核心配置文件中绑定接口

1
2
3
<mappers>
<mapper class="com.noob.dao.UserMapper"/>
</mappers>

1.@Param()注解

  • 基本类型的参数或者String参数需要加上
  • 引用类型不需要加
  • 只有一个基本类型时可省略
  • 在注解SQL语句引用的是@Param()中设定的属性名

2.#{}与${}:

1.#{}:占位符${}:拼接符

2.#{}:能防止sql注入${}:不能防止sql注入

3.能用#{}的地方就用#{},尽量少用${}


LomBok

LomBok

LomBok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。

@Getter:生成getter

@Setter:生成Setter

@ToString:生成toString()方法

@EqualsAndHashCode:生成hashCode() equals()

@AllArgsConstructor:生成包含所有字段的构造器

@RequiredArgsConstructor:生成必须初始化字段的构造器,如带final、@NonNull

@NoArgsConstructor:生成无参数构造器

@Data: 生成所有字段的getter toString() hashCode() equals() 所有非final字段的setter 构造器
@Builder @FieldNameConstants @SuperBuilder @Singular experimental @var @UtilityClass @Log @Log4j @Log4j2 @Slf4j @XSlf4j @CommonsLog @JBossLog @Flogger @CustomLog @Delegate @Value @Accessors @Wither @With @SneakyThrows @val @var

1.在IDEA中安装Lombok插件(File->Settings->Plugins)

IDEA2020.3版本以后,lombok已经集成到idea的plugins(Installed)当中了,pom.xml引入依赖即可。

2.在项目中导入lombok的jar包

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

private int id;
private String name;
private String password;

}

复杂查询

1.搭建数据库环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE `teacher` (
`id` INT (10) NOT NULL,
`name` VARCHAR (30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;

INSERT INTO teacher (`id`, `name`) VALUES (1, 'Mr.Qing') ;

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1,'Ming', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, 'Bob', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, 'Tom', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, 'Mike', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, 'Kate', 1);

2.创建实体类

1
2
3
4
5
6
7
8
9
10
import lombok.Data;

@Data
public class Student {
private int id;
private String name;

/*学生需要关联一个老师*/
private Teacher teacher;
}
1
2
3
4
5
6
7
import lombok.Data;

@Data
public class Teacher {
private int id;
private String name;
}

3.编写接口

1
2
3
4
5
6
import com.noob.pojo.Teacher;
import org.apache.ibatis.annotations.Param;

public interface TeacherMapper {
Teacher getTeacherByID(@Param("id") int id);
}

4.编写mapper(resource/com/noob/dao/TeacherMapper.xml)

resource新建多级目录使用/,如com/noob/dao

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.noob.dao.TeacherMapper">
<select id="getTeacherByID" resultType="com.noob.pojo.Teacher">
select * from mybatis.teacher where id=#{id}
</select>
</mapper>

5.注册mapper

1
2
3
4
<mappers>
<mapper resource="com/noob/dao/TeacherMapper.xml"/>
<mapper resource="com/noob/dao/StudentMapper.xml"/>
</mappers>

6.测试

1
2
3
4
5
6
7
public void testDemo1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = teacherMapper.getTeacherByID(1);
System.out.println(teacher.toString());
sqlSession.close();
}

多对一

1
select s.id,s.name,t.name from student s,teacher t where s.tid=t.id

查询嵌套

1
2
3
4
5
6
7
8
9
10
11
12
<select id="getStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="com.noob.pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂属性需要单独处理 对象:association 关联 多对一 集合:collection 一对多 -->
<association property="teacher" column="tid" javaType="com.noob.pojo.Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="com.noob.pojo.Teacher">
select * from teacher where id = #{tid}
</select>

结果嵌套

1
2
3
4
5
6
7
8
9
10
11
12
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname ,t.name tname
from student s,teacher t
where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="com.noob.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.noob.pojo.Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>

1
2
3
4
5
public interface StudentMapper {
public List<Student> getStudent();

public List<Student> getStudent2();
}

一对多

创建实体类

1
2
3
4
5
6
@Data
public class Student {
private int id;
private String name;
private int tid;
}
1
2
3
4
5
6
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
1
2
3
4
5
public interface TeacherMapper {
/*List<Teacher> getTeacher();*/
/*获取指定老师下的所有学生和老师信息*/
Teacher getTeacher(@Param("tid") int id);
}
1
2
3
4
5
6
@Test
public void testDemo1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
Teacher teacher = sqlSession.getMapper(TeacherMapper.class).getTeacher(1);
System.out.println(teacher.toString());
}

结果嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname, t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="com.noob.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--集合中的泛型信息,使用ofType获取-->
<collection property="students" ofType="com.noob.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>

查询嵌套

1
2
3
4
5
6
7
8
9
<select id="getTeacher" resultMap="TeacherStudent2">
select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="com.noob.pojo.Teacher">
<collection property="students" javaType="ArrayList" ofType="com.noob.pojo.Teacher" select="getStudentByTeacherId" column="id" />
</resultMap>
<select id="getStudentByTeacherId" resultType="com.noob.pojo.Student">
select * from mybatis.student where tid = #{id}
</select>

JavaType:用来指定实体类中属性的类型

ofType:用来指定映射到List或者集合中的pojo类型(泛型中的约束类型)


动态SQL

1.动态SQL是MyBatis的强大特性之一。动态SQL可根据不同的条件生成不同的SQL语句。(如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号)使用动态SQL并非一件易事,但借助可用于任何SQL映射语句中的强大的动态SQL语言,MyBatis显著地提升了这一特性的易用性。

2.MyBatis动态SQL元素:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

1.搭建数据库环境

1
2
3
4
5
6
7
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;

2.编写实体类

1
2
3
4
5
6
7
8
@Data
public class Blog {
private int id;
private String title;
private String author;
private Date createTime;
private int views;
}

3.编写mapper和接口并注册

1
2
3
public interface BlogMapper {
int addBlog(Blog blog);
}
1
2
3
4
5
6
<mapper namespace="com.noob.dao.BlogMapper">
<insert id="addBlog" parameterType="com.noob.pojo.Blog">
insert into mybatis.blog (id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
</mapper>
1
2
3
4
5
6
7
8
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--是否开启驼峰命名自动映射,即从经典数据库列名A_COLUMN映射到经典Java属性名 aColumn-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<mappers>
<mapper class="com.noob.dao.BlogMapper"/>
</mappers>

4.编写对应工具类和测试

1
2
3
4
5
6
@SuppressWarnings("all")
public class IDutils {
public static String getId(){
return UUID.randomUUID().toString().replace("-","");
}
}
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
@Test
public void testDemo1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("KuangStudy");
blog.setCreateTime(new Date());
blog.setViews(9999);

int n = mapper.addBlog(blog);

blog.setId(IDutils.getId());
blog.setTitle("Java");
n = mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Spring");
mapper.addBlog(blog);

blog.setId(IDutils.getId());
blog.setTitle("微服务");
mapper.addBlog(blog);

sqlSession.close();
}

if

使用动态SQL最常见情景是根据条件包含where子句的一部分。

1
2
3
4
5
6
7
8
9
<!--如果不传入title,那么所有处于ACTIVE状态的 BLOG 都会返回;如果传入了title参数,那么就会对 title一列进行模糊查找并返回对应的BLOG结果-->
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
1
2
3
4
5
6
7
8
9
10
11
<!--通过title和author两个参数进行可选搜索-->
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>

1
2
3
public interface BlogMapper {
List<Blog> queryBlogIf(Map map);
}
1
2
3
4
5
6
7
8
9
10
<!--1=1确保条件均不满足情况下返回所有结果-->
<select id="queryBlogIf" parameterType="map" resultType="com.noob.pojo.Blog" >
select * from mybatis.blog where 1=1
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testDemo2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap<String,String>();
/*
map.put("title","Spring");
map.put("author","KuangStudy");
*/
List<Blog> blogs = mapper.queryBlogIf(map);

for(Blog blog:blogs){
System.out.println(blog.toString());
}
sqlSession.close();
}

choose\when\otherwise

从多个条件中选择一个使用,MyBatis提供了choose元素,像Java中的switch语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--传入了title就按title查找,传入了author就按author查找的情形。若两者都没有传入,就返回标记为featured的BLOG -->
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="queryBlogChoose" parameterType="map" resultType="com.noob.pojo.Blog" >
select * from mybatis.blog
<where>
<choose>
<when test="title!=null">
title = #{title}
</when>
<when test="author!=null">
and author = #{author}
</when>
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testDemo2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap<String,String>();
map.put("title","Java");
map.put("views",99);
List<Blog> blogs = mapper.queryBlogChoose(map);

for(Blog blog:blogs){
System.out.println(blog.toString());
}
sqlSession.close();
}

trim\where\set

where元素只会在子元素返回任何内容的情况下才插入WHERE子句。而且,若子句的开头为ANDOR,where元素也会将它们去除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>

1
2
3
public interface BlogMapper {
List<Blog> queryBlogChoose(Map map);
}
1
2
3
4
5
6
7
8
9
10
11
<select id="queryBlogChoose" parameterType="map" resultType="com.noob.pojo.Blog" >
select * from mybatis.blog
<where>
<if test="title!=null">
title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</where>
</select>

可以通过自定义trim元素来定制where元素的功能。和where元素等价的自定义trim元素为:

1
2
3
4
<!--移除所有prefixOverrides属性中指定的内容,并且插入prefix属性中指定的内容-->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

prefixOverrides属性会忽略通过管道符分隔的文本序列(此例中的空格是必要的)


1.用于动态更新语句的类似解决方案叫做set。set元素可以用于动态包含需要更新的列,忽略其它不更新的列。

2.set元素会动态地在行首插入SET关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

与set元素等价的自定义trim元素(覆盖了后缀值设置,并且自定义了前缀值):

1
2
3
<trim prefix="SET" suffixOverrides=",">
...
</trim>

1
2
3
public interface BlogMapper {
int updateBlog(Map map);
}
1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title!=null">
title = #{title},
</if>
<if test="author!=null">
author = #{author}
</if>
</set>
where id = #{id}
</update>

foreach

对集合进行遍历(尤其是在构建IN条件语句的时候)

1
2
3
4
5
6
7
8
9
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>

1.foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

2.可以将任何可迭代对象(如 List、Set 等)、Map对象或者数组对象作为集合参数传递给foreach。当使用可迭代对象或者数组时,index是当前迭代的序号,item的值是本次迭代获取到的元素。当使用Map对象(或者 Map.Entry对象的集合)时,index是键,item是值。


1
2
3
public interface BlogMapper {
List<Blog> queryBlogForeach(Map map);
}
1
2
3
4
5
6
7
8
<select id="queryBlogForeach" parameterType="map" resultType="com.noob.pojo.Blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testDemo3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
map.put("ids",ids);
ids.add(1);
ids.add(3);
List<Blog> blogs = mapper.queryBlogForeach(map);
for(Blog blog:blogs){
System.out.println(blog.toString());
}
sqlSession.close();
}

SQL片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--方便代码复用-->
<select id="queryBlogIf" parameterType="map" resultType="com.noob.pojo.Blog" >
select * from mybatis.blog
<where>
<!--在需要的地方使用include标签引用即可-->
<include refid="if-title-author"></include>
</where>
</select>
<!--使用SQL标签抽取公共的部分-->
<sql id="if-title-author" >
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</sql>

1.最好基于单表来定义SQL片段。

2.SQL片段中不要使用where标签。


缓存

1.缓存:存在内存中的临时数据(将用户经常查询的数据放在缓存中,用户查询数据时无需从磁盘上(关系型数据库数据文件)查询,从缓存中查询,提高查询效率,解决高并发系统的性能问题。

2.使用缓存可减少和数据库的交互参数,减少系统开销,提高系统效率.

3.面对经常查询且不经常修改的数据可使用缓存。

Mybatis缓存

1.MyBatis内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。

3.默认情况下,只启用了本地的会话缓存(一级缓存 SqlSession级别缓存),它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只需要在你的SQL映射文件中添加一行:

1
<cache/>
  • 映射语句文件中的所有select语句的结果将会被缓存。
  • 映射语句文件中的所有insert、update 和 delete语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的1024个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

缓存只作用于 cache 标签所在的映射文件中的语句。如果混合使用Java API和XML映射文件,在共用接口中的语句将不会被默认缓存。需要使用@CacheNamespaceRef注解指定缓存作用域。


可以通过cache元素的属性来修改缓存的属性。

1
2
3
4
5
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

该配置创建了一个FIFO缓存,每隔60秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

1.flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

2.size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

3.readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是false。

可用的清除策略:

  • LRU – 最近最少使用:移除最长时间不被使用的对象(默认的清除策略)
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象

一级缓存

一级缓存默认开启,只在一次SqlSession中有效,即获得SqlSession到关闭SqlSession的过程。

1
2
3
4
5
public interface UserMapper {
User queryUserById(int id);

int updateUser(User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.noob.dao.UserMapper">
<select id="queryUserById" parameterType="_int" resultType="com.noob.pojo.User">
select * from mybatis.user where id = #{id}
</select>

<update id="updateUser" parameterType="com.noob.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id= #{id}
</update>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestDemo {
@Test
public void testDemo(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/*
缓存失效的情况:
1.查询不同的数据
2.增删改操作
3.查询不同的Mapper
4.手动清除缓存 sqlSession.clearCache();

*/
User user = mapper.queryUserById(1);
System.out.println(user.toString());
//mapper.updateUser(new User(1,"UU","23323232"));
sqlSession.clearCache();
System.out.println("===========");
User user2 = mapper.queryUserById(1);
System.out.println(user2.toString());
sqlSession.close();
}
}

二级缓存

1.二级缓存基于namespace级别的缓存,一个名称空间对应一个二级缓存。

2.工作机制

  • 当会话关闭时,该会话对应的一级缓存被清除,新的会话查询数据可从二级缓存获取(会话提交或关闭时)。
  • 不同的mapper查询的数据会放在对应的缓存中。

1.开启全局缓存(默认开启)

1
2
3
4
<settings>
<!--显式开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>

2.在要使用二级缓存的Mapper中开启

1
2
3
4
5
<!--在当前Mapper.xml中使用缓存-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestDemo {
@Test
public void testDemo(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

User user = mapper.queryUserById(1);
System.out.println(user.toString());
sqlSession.close();

System.out.println("===========");
User user2 = mapper2.queryUserById(1);
System.out.println(user.toString());
sqlSession.close();
}
}

缓存原理

用户查询数据:

1.检查二级缓存是否存在该数据

2.检查一级缓存是否存在该数据

3.查询数据库

EhCache

EhCache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

1.导入依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>

2.导入Ehcache

1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

3.配置文件(resource->ehcahe.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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>

</ehcache>


Spring

简介

Spring

spring-framework

Docs

Download

中文文档

Spring框架是Java平台的一个开源的全栈(full-stack)应用程序框架和控制反转容器实现,一般被直接称为 Spring。该框架的一些核心功能理论上可用于任何Java应用,但Spring还为基于Java企业版平台构建的 Web 应用提供了大量的拓展支持。Spring没有直接实现任何的编程模型,但它已经在Java社区中广为流行,基本上完全代替了企业级JavaBeans(EJB)模型。Spring框架以Apache License 2.0开源许可协议的形式发布,该框架最初由 Rod Johnson以及Juergen Hoeller等人开发。


1.控制反转容器(依赖注入,IOC,Inverse Of Control):即把创建对象的权利交给框架,也就是指将对象的创建、对象的存储、对象的管理交给了Spring容器。Spring容器是Spring中的一个核心模块,用于管理对象,底层可以理解为是一个Map集合。
2.面向切面编程(Aspect-Oriented Programming, AOP): 就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任分开封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

3.数据访问(DAO层支持):Spring Data实现了对数据访问接口的统一,支持多种数据库访问框架或组件(如:JDBC、Hibernate、MyBatis(iBatis))作为最终数据访问的实现。

4.事务管理:Spring框架为事务管理提供了一致的抽象,具有以下优点:

  • 跨不同事务API(如:Java事务、JDBC、Hibernate和Java Persistence API事务(JPA))的一致编程模型
  • 支持声明式事务
  • 与诸如JTA之类的复杂事务API相比,用于过程化事务管理的API更简单
  • 与Spring的数据访问抽象出色地集成

5.模型-视图-控制器(MVC):Spring MVC 实现了基于MVC设计方法的实现,结合基于Java注解的配置,允许开发者开发出低代码侵入的Web应用项目,并简便地实现大部分Web功能(包括请求参数注入、文件上传控制等)。


组成


1.SpringBoot

Spring Boot是由Pivotal团队提供的全新框架,目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

2.Spring Cloud

1.Spring Cloud是一系列框架的有序集合。

2.利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

3.Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。


IOC思想

1.控制反转(Inversion of Control,IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,DI),还有一种方式叫依赖查找(Dependency Lookup)。

2.Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式地用new创建 B 的对象。采用依赖注入技术之后,A的代码只需要定义一个private 的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

3.未没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行时,必须去创建对象B或者使用已经创建的对象B。引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方,即对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权反转。


1
2
3
public interface UserDao {
void getUser();
}
1
2
3
4
5
public class UserDaoImpl implements UserDao {
public void getUser(){
System.out.println("-->UserDaoImpl:getUser");
}
}
1
2
3
4
5
public class UserDaoMysqlImpl implements UserDao {
public void getUser(){
System.out.println("-->UserDaoMysqlImpl:getUser");
}
}
1
2
3
4
5
public class UserDaoOracleImpl implements UserDao {
public void getUser() {
System.out.println("-->UserDaoOracleImpl:getUser");
}
}
1
2
3
public interface UserService {
void getUser();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserServiceImpl implements UserService {

private UserDao userDao;
/*利用set实现动态值的注入*/
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
/*
private UserDao userDao = new UserDaoMysqlImpl();
private UserDao userDao = new UserDaoImpl();
*/
public void getUser() {
userDao.getUser();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyTest {
public static void main(String[] args) {
/*用户实际调用业务层(service),无需接触dao层*/
UserService userService = new UserServiceImpl();
/*
1.未在UserService接口定义setUserDao方法,上转型对象只能调用父类未重写的方法或者子类已重写的方法
,所以此处userService需要强制转换UserServiceImpl类型调用setUserDao方法
2.使用Set注入,无需程序主动创建对象,如
private UserDao userDao = new UserDaoMysqlImpl();
private UserDao userDao = new UserDaoImpl();
去修改userDao的实现方式,而是被动接受对象,无需关注对象的创建,系统耦合性降低。专注与
业务实现,即专注UserDao的实现类,这就是IOC的原型
*/
((UserServiceImpl)userService).setUserDao(new UserDaoOracleImpl());
userService.getUser();
}
}

->理解

IOC思想,相当于你是一个厨师,原来你面对客户的不同口味选择用不同的盘子装不同的菜端上,而在IOC思想下选择先交给统一客户一个盘子让其自己盛菜即可。


Spring IOC

0.maven依赖(pom.xml)

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.13</version>
</dependency>
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.12</version>
</dependency>

1.Spring IoC容器和Bean简介

1.org.springframework.beansorg.springframework.context包是Spring Framework的IoC容器的基础。 BeanFactory接口提供了一种高级配置机制,能够Management任何类型的对象。ApplicationContext是BeanFactory的子接口。

2.在Spring中,构成应用程序主干并由Spring IoC容器Management的对象称为bean。Bean是由 Spring IoC容器实例化,组装和以其他方式Management的对象。否则,bean仅仅是应用程序中许多对象之一。Bean 及其之间的依赖关系反映在容器使用的配置元数据中

3.org.springframework.context.ApplicationContext接口代表Spring IoC容器,并负责实例化,配置和组装Bean。容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以XML,Java注解或Java代码表示。Spring提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。

4.Spring配置由容器必须Management的至少一个(通常是一个以上)bean定义组成。基于XML的配置元数据将这些bean配置为顶级<beans/>元素内的<bean/>元素。 Java配置通常在@Configuration类中使用@Bean注解的方法。

基于XML的配置元数据的基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions go here -->

</beans>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {
private String name;

public String getName() {
return name;
}

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

@Override
public String toString() {
return "User [name=" + name + "]";
}
}
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring创建对象时,在Spring中称为Bean-->
<bean id="user" class="com.ling.pojo.User">
<property name="name" value="SpringBoot"/>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
public class MyTest {
public static void main(String[] args) {
/*获取Spring的上下文对象*/
/*提供给ApplicationContext构造函数的位置路径是资源字符串,这些资源字符串使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH等)加载配置元数据。*/
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User)context.getBean("user");
System.out.println(user.toString());
}
}

IDEAXML配置文件上方通常会出现Application context not configured for this file:选择Configure application context即可


1
2
3
4
5
6
<bean id="mysqlImpl" class="com.ling.dao.UserDaoMysqlImpl"/>
<bean id="oracleImpl" class="com.ling.dao.UserDaoOracleImpl"/>

<bean id="UserServiceImpl" class="com.ling.service.UserServiceImpl">
<property name="userDao" ref="mysqlImpl"/>
</bean>
1
2
3
4
5
6
7
public class MyTest {
public static void main(String[] args) {
/*获取ApplicationContext:拿到Spring容器*/
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
}

2.IOC创建对象的方式

1.默认使用无参构造创建对象。

2.使用有参构造创建对象:

  • 构造函数参数索引:使用index属性来显式指定构造函数参数的索引。
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
  • 构造函数参数类型匹配:使用type属性显式指定了构造函数参数的类型,则容器可以使用简单类型的类型匹配。
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
  • 构造函数参数名称:使用构造函数参数名称来消除歧义。
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
private String name;
public Student() {
System.out.println("->Student无参构造");
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ling.pojo;

public class User {
private String name;
public User(){
System.out.println("->User无参构造");
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyTest {
public static void main(String[] args) {
/*
容器创建时对beans中 无有参构造配置对象调用无参构造进行初始化 有参构造对象调用有参构造进行初始化
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
*/
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user==user2);
Student student = (Student) context.getBean("student");
System.out.println(user.getName());
System.out.println(student.getName());
}
}
1
2
3
4
5
6
7
8
9
10
11
<bean id="user" class="com.ling.pojo.User">
<constructor-arg index="0" value="Admin"/>
</bean>
<!--
<bean id="student" class="com.ling.pojo.Student">
<constructor-arg type="java.lang.String" value="Sun"/>
</bean>
-->
<bean id="student" class="com.ling.pojo.Student">
<constructor-arg name="name" value="DFM"/>
</bean>

Spring配置

1.Alias

1
2
3
4
5
6
7
8
<bean id="user" class="com.ling.pojo.User">
<constructor-arg index="0" value="Admin"/>
</bean>
<alias name="user" alias="User"/>
<!--
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("User");
-->

2.Bean Property

1.id:是标识单个bean定义的字符串。

2.class属性:定义bean的类型并使用完全限定的类名。

3.property

  • name:引用JavaBean属性的名称
  • value:设置该属性的值

4.ref:引用另一个bean定义的名称,即Spring容器创建的对应对象

5.name:为bean引入其他多个别名,以逗号 (,), 分号 (;) 或空格。

Property Explained
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Constructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

3.import

当配置文件内容较多,为了便于管理,可以把配置文件按一定的规则拆成几个文件,在加载主配置文件通过<import>元素引入所引来的配置文件。

1
<import resource="beans.xml"/>

依赖注入DI

依赖注入(DI)是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法的参数或在构造或创建对象实例或从工厂方法返回后在对象实例上设置的属性来定义其依赖关系(即与它们一起工作的其他对象)。然后,容器在创建bean时注入那些依赖项。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来自己控制其依赖关系的实例化或位置的Bean本身的逆过程(Control Inversion)。

使用DI原理,代码更简洁,当为对象提供依赖项时,去耦会更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。结果类变得更易于测试,尤其是当依赖项依赖于接口或抽象Base Class时,它们允许在单元测试中使用存根或模拟实现。

1.基于构造函数的依赖注入:通过容器调用具有多个参数(每个参数代表一个依赖项)的构造函数完成。

1
2
3
4
5
6
7
8
9
10
11
public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;

// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}

2.基于Setter的依赖注入:基于Setter的DI是通过在调用无参数构造函数或无参数static工厂方法以实例化bean之后,在bean上调用setter方法来完成的。(bean对象的创建依赖于容器,bean对象的所有属性由容器注入)

1
2
3
4
5
6
7
8
9
10
11
12
public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;

// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}

// business logic that actually uses the injected MovieFinder is omitted...
}

1
2
3
4
5
6
@Getter
@Setter
@ToString
public class Address {
private String address;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@Setter
@ToString
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
}
1
2
3
4
5
6
7
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.toString());
}
}

<list/> <set/> <map/><props/>元素设置属性和Java的参数Collection类型List Set MapProperties

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.ling.pojo.Address">
<property name="address" value="天津"/>
</bean>

<bean id="student" class="com.ling.pojo.Student">
<!--普通值注入 value-->
<property name="name" value="Ge"/>
<!-- Bean注入 ref -->
<property name="address" ref="address"/>
<!-- 数组 -->
<property name="books">
<array>
<value>Java入门</value>
<value>Java入坟</value>
</array>
</property>
<!--List-->
<property name="hobbies">
<list>
<value>Code</value>
<value>Read</value>
<value>Sleep</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="学号" value="2902302"/>
<entry key="班级" value="计算机1班"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>Apex</value>
<value>CSGO</value>
<value>Aim Lab</value>
</set>
</property>
<!--
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
等效于
exampleBean.setEmail("");

<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
等效于:
exampleBean.setEmail(null);
-->

<property name="wife">
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="sex"></prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>

3.p命名空间和c命名空间注入

1.p命名空间和c命名空间不能直接使用,需要导入xml约束。

1
2
xmlns:p="http://www.springframework.org/schema/p" 
xmlns:c="http://www.springframework.org/schema/c"

2.p-namespace允许使用bean元素的属性(而不是嵌套的<property/>元素)来描述协作Bean的属性值,或同时使用这两者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>

<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>

<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>

第一个bean定义使用<property name="spouse" ref="jane"/>创建从beanjohn到 beanjane的引用,而第二个bean定义使用p:spouse-ref="jane"作为属性来执行完全相同的操作。在这种情况下,spouse是属性名称,而-ref部分表示这不是一个直接值,而是对另一个bean的引用。


1
2
3
4
5
6
7
8
9
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
1
2
3
4
5
6
7
8
9
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--p命名空间注入可直接注入属性 property-->
<bean id="user" class="com.ling.pojo.User" p:age="12" p:name="Mike"/>
</beans>
1
2
3
4
5
6
7
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user",User.class);
System.out.println(user.toString());
}
}

3.c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套的constructor-arg元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>

<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>

<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

4.c:名称空间使用与p:相同的约定(对于Bean引用,尾随-ref)以其名称设置构造函数参数。同样,即使未在 XSD模式中定义它(也存在于Spring内核中)也需要在XML文件中声明它。

5.对于极少数情况下无法使用构造函数自变量名称的情况,可以使用参数索引。(由于XML语法的原因,索引符号要求前导_的存在,因为XML属性名称不能以数字开头(即使某些IDE允许))

1
2
3
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>

1
2
<!--c命名空间通过构造器注入 construct-->
<bean id="user" class="com.ling.pojo.User" c:age="18" c:name="CoCo"/>

Bean作用域

Scope Description
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.

1.The Singleton Scope:单例模式

如果您在单个Spring容器中为特定类定义一个bean,则 Spring 容器将创建该bean定义所定义的类的一个且只有一个实例。 Singleton范围是Spring中的默认范围。要将bean定义为XML中的单例,也可以显示定义bean

1
2
3
4
<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

2.The Prototype Scope:原型模式

每次对特定bean发出请求时,会创建一个新bean实例

1
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

3.session request application通常用于Web开发。


Bean自动装配

Spring容器可以在不使用<constructor-arg><property>元素的情况下自动装配相互协作的bean之间的关系(装配属性),有助于减少XML配置编写。

Autowiring modes

Mode Explanation
no (Default) No autowiring. Bean references must be defined by ref elements. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system.
byName Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master and uses it to set the property.
byType Lets a property be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens (the property is not set).
constructor Analogous to byType but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.

1.BynName自动装配

当一个bean节点带有autowire="byName"的属性时:

①将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。

②去spring容器中寻找是否有对应bean id的对象。

③如果有就取出注入,没有则报空指针异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="cat" class="com.ling.pojo.Cat"/>
<bean id="dog" class="com.ling.pojo.Dog"/>
<!--
byName:会自动在容器上下文寻找和对象set方法的值对应的bean id
byType:会自动在容器上下文寻找和对象属性类型相同的bean
ByName需要确保所有bean的id唯一 ByType需要所有bean的class唯一
-->
<bean id="people" class="com.ling.pojo.People" autowire="byName">
<property name="name" value="Simple"/>
<!--
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
-->
</bean>

2.ByType自动装配

使用autowire="byType"需要保证:注入的同一类型对象在spring容器中唯一。按类型装配,id属性(id可属性去掉)不影响自动装配结果。

1
2
3
4
5
6
<bean  class="com.ling.pojo.Cat"/>
<bean class="com.ling.pojo.Dog"/>

<bean id="people" class="com.ling.pojo.People" autowire="byType">
<property name="name" value="Simple"/>
</bean>

3.注解自动装配

  • 配置注解支持
1
2
3
4
5
6
7
8
9
10
11
12
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

</beans>

1.@Autowired/@Resource

1.@Autowired可以在实例字段(无需编写对应set方法)以及setter 构造函数上使用。@Autowired注解默认按照byType,如果有多个class类型相同的bean,则通过byName(修改bean的id并不会影响自动装配,但修改class便会报错)装配依赖对象(确保自动装配的属性在IOC(Spring)容器中存在)。

2.@Autowired(required=false) 即该对象可为null,否则不允许为空。

3.@Qualifier(value ="")配合@Autowired的使用可指定一个唯一的bean对象注入。

4.@Resource 和 @Autowired,两者的区别,@Resource 是 javax.annotation.Resource包下,是JavaEE支持的;@Autowiredorg.springframework.beans.factory.annotation.Autowired包下,是spring提供的,这会导致@Resource的可扩展性优于 @Autowired,因为在导出一个项目时,使用 @Resource注解,可以通过Java环境提供,而使用@Autowired注解需要额外引入spring包。

1
2
3
4
/* @Nullable说明该字段可为null*/
public People(@Nullable String name) {
this.name = name;
}
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
public class People {
@Autowired
private Dog dog;
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
private String name;

public Dog getDog() {
return dog;
}
/*
public void setDog(Dog dog) {
this.dog = dog;
}
*/
public Cat getCat() {
return cat;
}
/*
public void setCat(Cat cat) {
this.cat = cat;
}
*/
public String getName() {
return name;
}

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

@Override
public String toString() {
return "People{" +
"dog=" + dog +
", cat=" + cat +
", name='" + name + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat2" class="com.ling.pojo.Cat"/>
<bean id="cat1" class="com.ling.pojo.Cat"/>
<bean id="dog" class="com.ling.pojo.Dog"/>
<bean id="people" class="com.ling.pojo.People" />
</beans>

1
2
3
4
5
6
7
8
9
10
public class People {
@Resource
private Dog dog;
/*@Resource(name="cat1")*/
@Resource
private Cat cat;
private String name;

/*Code*/
}
1
2
3
<bean id="cat" class="com.ling.pojo.Cat"/>
<bean id="cat1" class="com.ling.pojo.Cat"/>
<bean id="dog" class="com.ling.pojo.Dog"/>

注解开发

@Component: 标注普通的 Spring Bean 类(将 pojo 类实例化到 spring 容器中,相当于配置文件中的<bean id="" class=""/> )

1.属性注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*Component:组件 相当于 <bean id="user" class="com.ling.pojo.User"/>*/
@Component
@Scope("singleton") /*单例模式*/
public class User {
/*相当于<property name="" value=""/>*/
@Value("SpringStudy")
public String name;

public int age;

public int getAge() {
return age;
}

@Value("12")
public void setAge(int age) {
this.age = age;
}
}

2.衍生注解(按MVC三层框架分层):

  • dao层: @Repository(标注DAO组件类)

  • service层:@Service(标注业务逻辑组件类)

  • controller层:@Controller(标注控制器组件类)

这四个注解功能都是一样的,都是将对应类注册到Spring容器中,装配Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<!--指定要扫描的包,该包下注解生效-->
<context:component-scan base-package="com.ling"/>
<context:annotation-config/>

</beans>

JavaConfig配置

1.@Configuration注解

 定义 JavaConfig 类加上 @Configuration 注解表明这是一个配置类,@Configuration 注解底层是

@Component 注解,该注解需要用 AnnotationConfigApplicationContext 进行加载加载。

AnnotationConfigApplicationContext 是 ApplicationContext 的一个具体实现,代表依据配置注解启动应用上下文。

2.@ComponentScan注解

  使用 JavaConfig 目的是为了实现 XML 配置实现的功能,首先需要组件扫描功能,将使用特定注解标注的类统一扫描加载到 Spring 容器,这一功能依靠 @ComponentScan 注解来实现,可以为其指定位置参数来指定要扫描的包。

3.@Bean注解

使用 @Bean 注解可以实现 XML 配置中手动配置第三方 Bean 的功能,这里使用方法来定义 Bean,并在方法前面加 @Bean 注解,表示要将该方法返回的对象加载到 Spring 容器中:

  • 方法带返回值(bean class),且返回类型为你要加载的第三方类类型(要注入到bean的对象)
  • 方法的名称为默认的 Bean 的 name(Bean id),如果要自定义 Bean 的 name ,可以使用 @Bean 注解的 name 属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class User {
private String name;

public String getName() {
return name;
}

@Value("Rick")
public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
@Configuration
@ComponentScan("com.ling.pojo")
public class MyConfig {
@Bean(name = "user")
public User getUser(){
return new User();
}
}
1
2
3
4
5
6
7
8
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user",User.class);
System.out.println(user.toString());

}
}

AOP

代理模式

代理模式(Proxy Pattern)程序设计中的一种设计模式。

所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。

当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。


1.静态代理

  • 抽象角色:通常使用抽象类或接口
  • 真实角色:被代理角色(无需关注公共业务)
  • 代理角色:代理真实角色,通常会做附属操作(实现业务分工,在公共业务出现扩展时,便于集中管理)
  • 客户:访问代理角色

一个真实角色会产生一个代理角色,代码量增多,降低开发效率

1
2
3
4
public interface Rent {
public void rent();

}
1
2
3
4
5
6
public class Landlord implements Rent {

public void rent() {
System.out.println("The landlord rents the house");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*Proxy*/
public class Mediator implements Rent {
private Landlord landlord;

public Mediator() {

}
public Mediator(Landlord landlord) {
this.landlord = landlord;
}
public void rent() {
visitHouse();
landlord.rent();
pay();
}

public void visitHouse() {
System.out.println("The agent takes the client to visit the house");
}

public void pay() {
System.out.println("The customer pays for the rental");
}
}
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
/*Proxy*/
public class Mediator implements Rent {
private Landlord landlord;

public Mediator() {

}
public Mediator(Landlord landlord) {
this.landlord = landlord;
}
public void rent() {
visitHouse();
landlord.rent();
pay();
}

public void visitHouse() {
System.out.println("The agent takes the client to visit the house");
}

public void pay() {
System.out.println("The customer pays for the rental");
}
}

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {

Mediator mediator = new Mediator(new Landlord());
mediator.rent();
}

}

2.动态代理

动态代理的代理类是动态生成的,而非直接写好的

  • 基于接口:JDK原生动态代理(Proxy InvocationHandler)
  • 基于类:cglib
  • 基于Java字节码实现:javasist
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
public class ProxyInvocationHandler implements InvocationHandler {
/*被代理接口*/
/*
private Rent rent;

public void setRent(Rent rent) {
this.rent = rent;
}
*/
private Object target;

public void setTarget(Object target) {
this.target = target;
}
/*得到代理类*/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/*处理代理实例并返回结果 利用反射机制*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
visitHouse();
Object result = method.invoke(target, args);
pay();
return result;
}

public void visitHouse() {
System.out.println("The agent takes the client to visit the house");
}

public void pay() {
System.out.println("The customer pays for the rental");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();

/*通过调用程序处理角色来处理需要调用的接口对象*/
proxyInvocationHandler.setTarget(new Landlord());;
/*获得动态代理生成的类*/
Rent proxy = (Rent) proxyInvocationHandler.getProxy();
proxy.rent();
}

}

AOP简介

面向切面编程(Aspect-oriented programming,AOP)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。

通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为"切点(Pointcut)"的代码块进行统一管理与装饰,如"对所有方法名以'set*'开头的方法添加后台日志"。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点"横切"程序代码中的数个模块,即在多个模块中都有出现,它们即被称作横切关注点(Cross-cutting concerns, Horizontal concerns)。

日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即”横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。

切面的概念源于对面向对象的程序设计和计算反射的融合,但并不只限于此,它还可以用来改进传统的函数。与切面相关的编程概念还包括元对象协议、主题(Subject)、混入(Mixin)和委托(Delegate)。


Spring AOP

  • 横切关注点(Crosscutting Concern):跨越应用程序多个模块的方法或功能,即与业务逻辑代码无关但需要关注的部分,如日志、安全、缓存、事务等。

  • 切面(aspect):横切关注点被模块化的特殊对象(类)。

  • 通知(Adivice):切面必须要完成的工作(类中的方法)。

  • 目标(Target):被通知对象。

  • 代理(proxy):向目标对象应用通知之后创建的对象。

  • 切入点(pointcut):切面通知执行的'地点'的定义。

  • 连接点(joinpoint):与切入点匹配的执行点。

在Spring AOP中通过Advice定义横切逻辑,Spring中支持五种类型的Advice:

类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.BeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法和属性 org.springframework.aop.introductioninterceptor

导入依赖

1
2
3
4
5
6
7
8
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
<!--使用注解时去掉runtime元素-->
</dependency>

1.Spring API接口实现

1
2
3
4
5
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("Executed the "+method.getName()+" method,Return result:"+returnValue);
}
}
1
2
3
4
5
6
public class BeforeLog implements MethodBeforeAdvice {
/*method:要执行的目标对象的方法 args:参数 target:目标对象*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+":"+method.getName()+" Be executed");
}
}
1
2
3
4
5
6
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("->Add User");
}

@Override
public void delete() {
System.out.println("->Delete User");
}

@Override
public void update() {
System.out.println("->Update User");
}

@Override
public void select() {
System.out.println("->Select User");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--注册bean-->
<bean id="userService" class="com.ling.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.ling.log.BeforeLog"/>
<bean id="afterLog" class="com.ling.log.AfterLog"/>
<!--配置aop 需要导入aop约束-->
<aop:config>
<!--切入点 expression:表达式 execution(修饰词 返回值 列名 方法名 参数) -->
<aop:pointcut id="pointcut" expression="execution(* com.ling.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
1
2
3
4
5
6
7
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService",UserService.class);
userService.delete();
}
}

Spring AOP The matching wildcard is strict, but no declaration can be found for element ‘aop:config 错误:在 bean 配置文件中加入

1
2
3
4
5
6
xsi:schemaLocation="
...
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
...
"

2.自定义切面实现AOP

1
2
3
4
5
6
7
8
class CustomPointCut {
public void before(){
System.out.println("====Before the method is executed====");
}
public void after(){
System.out.println("====After the method is executed====");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--注册Bean-->
<bean id="userService" class="com.ling.service.UserServiceImpl"/>
<bean id="custom" class="com.ling.customize.CustomPointCut"/>

<aop:config>
<!--自定义切面-->
<aop:aspect ref="custom">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.ling.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>

3.注解实现AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect /*标注该类为切面*/
public class AnnotationPointCut {
@Before("execution(* com.ling.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("->Before the method is executed<-");
}
@After("execution(* com.ling.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("->After the method is executed<-");
}
@Around("execution(* com.ling.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before");
Object proceed = jp.proceed(); /*执行方法*/
System.out.println("around after");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--注册Bean-->
<bean id="userService" class="com.ling.service.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.ling.customize.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
</beans>

Mybatis-Spring

Docs

导入依赖(pom.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
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
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>SpringStudy</artifactId>
<groupId>com.ling</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>Spring-09-mybatis</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

  • SqlSessionTemplate

1.编写实体类和Mapper

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
1
2
3
public interface UserMapper {
public List<User> selectUser();
}
1
2
3
4
5
6
7
8
9
10
<!--UserMapper.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.mapper.UserMapper">
<select id="selectUser" resultType="User">
select * from mybatis.user
</select>
</mapper>

2.配置数据源(Mybatis.xml->Spring.xml) sqlSessionFactory SqlSessionTemplate

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
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<!--DataSource 使用Spring的数据源(DriverManagerDataSource类)替换Mybatis配置(Mybatis-config.xml) c3p0 dbcp druid-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="0513"/>
</bean>

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource" />
<!--绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/ling/mapper/*.xml"/>
</bean>
<!--
SqlSessionTemplate 是 MyBatis-Spring 的核心。
作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入sqlSessionFactory,因为其没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>


<bean id="userMapper" class="com.ling.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--别名管理-->
<typeAliases>
<package name="com.ling.pojo"/>
</typeAliases>
</configuration>

3.接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserMapperImpl implements UserMapper{
/*原来使用SqlSession来执行,现在使用SqlSessionTemplate*/
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate){
this.sqlSessionTemplate = sqlSessionTemplate;
}

public List<User> selectUser() {
UserMapper userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
return userMapper.selectUser();
}

}

4.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
@Test
public void testDemo(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = context.getBean("userMapper",UserMapper.class);
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user.toString());
}

}
}

  • SqlSessionDaoSupport
1
2
3
4
5
6
7
8
9
10
11
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{

public List<User> selectUser() {
/*
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.selectUser();
*/
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}
1
2
3
<bean id="userMapper2" class="com.ling.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

Spring事务管理

1.声明式事务

2.编程式事务

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

ACID原则

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

1.声明式事务

1
2
3
4
5
6
7
public interface UserMapper {
public List<User> selectUser();

public int addUser(User user);

public int deleteUser(int id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.mapper.UserMapper">
<select id="selectUser" resultType="User">
select * from mybatis.user
</select>

<insert id="addUser" parameterType="user">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

public List<User> selectUser() {
User user = new User(9,"UU","2gfg");

SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
return mapper.selectUser();

}

@Override
public int addUser(User user) {

return getSqlSession().getMapper(UserMapper.class).addUser(user);
}

@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
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
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--DataSource 使用Spring的数据源(DriverManagerDataSource类)替换Mybatis配置(Mybatis-config.xml) c3p0 dbcp druid-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="0513"/>
</bean>

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource" />
<!--绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/ling/mapper/*.xml"/>
</bean>
<!--
SqlSessionTemplate 是 MyBatis-Spring 的核心。
作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入sqlSessionFactory,因为其没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>

<bean id="userMapper" class="com.ling.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<!--结合AOP实现事务插入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给方法配置事务 配置事务的传播特性-->
<!--
Spring中七种Propagation类的事务属性详解:
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.ling.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>