MyBatis
MyBatis
思维导图:点击打开
1.框架概述
1.1三层架构
三层架构分别是:
界面层(User Interface Layer)、业务逻辑层(Business Logic Layer)、数据访问层(Date Access Layer)。
- 界面层(表示层/视图层):和用户打交道的,用于接收用户发送过来的请求参数,显示请求的处理结果。
- 业务逻辑层:用于接收界面层传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
- 数据访问层(持久层):和数据库打交道的,主要实现对表中数据的增删查改。
三层架构对应的包:
- 界面层:controller包(servlet)
- 业务逻辑层:service包(XXXService类)
- 数据访问层:dao包(XXXDao类)
三层架构间处理请求的交互:
三层架构对应的处理框架:
- 界面层:servlet---->SpringMVC(框架)
- 业务逻辑层:service类---->Spring(框架)
- 数据访问层:dao类---->MyBatis(框架)
1.2框架的概念
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件以及构件实例间交互的方法。简单来说,框架就是半成品软件,就是一组组件,供你完成自己的系统。
另一种说法认为,框架是可被应用开发者定制的应用骨架、模板。比如说常见的ppt模板和简历模板,都是预先规定好了一些条款和内容样式,然后再加入自己的东西,实现自己需要的功能。
框架是安全的,可复用的,不断升级的软件。
1.3MyBatis框架概述
1.3.1什么是MyBatis?
Mybatis是MyBatis SQL Mapper Framework for Java(SQL映射框架)
1)SQL Mapper:SQL映射
- 可以把数据库表中的一行数据映射为一个java对象。
- 一行数据就可以看作是一个java对象。操作这个对象,就相当于操作表中的数据。
2)Data Access Objects(DAOs):数据访问,对数据库执行增删改查
- MyBatis是一款优秀的持久层框架。
- 它支持自定义SQL、存储过程以及高级映射。
- MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
- MyBatis可以通过简单的XML或注解,来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
- MyBatis本是apache的一个开源项目iBatis,2010年这个项目迁移到了google code,并改名为MyBatis。
- 2013年11月迁移到Github。
1.3.2如何获取MyBatis?
1.第一种:添加依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2.第二种:下载jar包
GitHub:https://github.com/mybatis/mybatis-3/releases
3.中文帮助文档:https://mybatis.org/mybatis-3/zh/index.html
1.3.3什么是数据持久层?
数据持久化是将程序中的数据从瞬时状态转化为持久状态的过程。
数据在内存中时,断电即失。为了让一些重要的数据持久化,可以使用JDBC等将数据保存到数据库中。(IO文件持久化)。
数据持久层就是完成数据持久化的代码模块。
1.3.4为什么需要MyBatis取代JDBC?
JDBC的缺陷:
- 代码比较多,开发效率低
- 代码冗余
- 需要手动对Connection、Statement和ResultSet对象进行创建和销毁
- 对ResultSet查询的结果需要自己封装进集合
- 业务代码和数据库的操作混合在一起
MyBatis提供的功能:
- 提供了自动创建Connection、Statement和ResultSet对象的能力,不用开发人员手动创建了
- 提供了自动执行sql语句的能力,不用开发人员手动执行语句
- 提供了自动处理查询结果集的能力,自动把sql的结果转为java对象、List集合
- 提供了自动关闭资源的能力,不需要开发人员手动关闭
- 开发人员只需要提供sql语句给MyBatis,Mybatis会自动处理sql语句,返回存放着表中数据的List集合或java对象。
1.3.5总结
MyBatis是一个sql映射框架,提供对数据库的操作功能,是一个增强的JDBC。
使用MyBatis可以让开发人员集中精神写SQL语句,不用关心Connection、Statement、ResultSet对象的创建和销毁以及SQL语句的执行。
2.编写一个MyBatis程序
2.1实现步骤
思路:搭建环境---->导入MyBatis---->编写代码---->测试
步骤:
1.创建数据库及数据库表
2.创建maven项目,添加依赖和插件
3.创建数据库表对应实体类
4.创建持久层的Dao接口,定义操作数据库的方法
5.创建一个mybatis使用的配置文件,叫sql映射文件。
- 用于写sql语句,一般一个表一个sql映射文件
- 文件类型是xml
- 在步骤4的Dao接口同级目录下创建,要求文件名称与接口名一致。
6.创建mybatis的主配置文件
- 一个项目只有一个主配置文件
- 主配置文件提供了数据库的连接信息和sql映射文件的位置信息
7.创建使用mybatis的类,通过mybatis访问数据库
2.2创建数据库及数据库表
sql语句:
create database `db_mybatis`;
use `db_mybatis`;
create table `t_student`(
`id` int primary key,
`name` varchar(255) default null,
`email` varchar(255) default null,
`age` int default null
)engine=InnoDB default charset=utf8;
insert into `t_student`(`id`,`name`,`email`,`age`) values
(1001,'张三','zhangsan@qq.com',20),
(1002,'李四','lisi@qq.com',21),
(1003,'王五','wangwu@qq.com',22);
select * from `t_student`;
2.3创建maven项目
1.新建一个普通的父maven项目【Mybatis-Project】
2.删除父maven项目的src目录
3.在pom.xml文件里添加maven依赖和maven插件
<?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.tsccg</groupId>
<artifactId>MyBatis-Project</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<!-- 添加maven依赖 -->
<dependencies>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- mybatis依赖 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mysql驱动依赖 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
</dependencies>
<!--添加maven插件-->
<build>
<!-- 指定编译时扫描范围 -->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties和.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory><!--所在的目录-->
<includes><!--包括目录下的.properties和.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<!-- 配置jdk版本为1.8 -->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.创建一个子模块
创建一个子maven模块【mybatis-01】,这个子模块可以继承父模块的所有配置
2.4创建数据库表对应实体类
在子模块的main/java目录下,创建t_student表对应实体类【com.tsccg.entity.Student】
实体类用于存放表中的某一条记录信息
package com.tsccg.entity;
/**
* @Author: TSCCG
* @Date: 2021/09/06 21:40
* t_student表对应实体类
*/
public class Student {
//定义属性名,要求和t_student表的列名一致
private Integer id;
private String name;
private String email;
private Integer age;
public Student() {
}
public Student(Integer id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
2.5创建持久层的Dao接口
在子模块的main/java目录下,创建Dao接口【com.tsccg.dao.StudentDao】
在Dao接口中定义操作数据库的方法,这里先只定义了查询方法
package com.tsccg.dao;
import com.tsccg.entity.Student;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/06 21:59
* 接口操作t_student表
*/
public interface StudentDao {
/**
* 查询表中所有数据
* @return 返回一个存放Student对象的List集合
*/
List<Student> findAll();
}
2.6创建sql映射文件
一个mybatis使用的配置文件,叫sql映射文件,也叫sql mapper文件。
在上一步创建的StudentDao接口同级目录下,创建一个同名的映射文件【StudentDao.xml】
在映射文件里编写sql语句,mybatis会执行编写的sql语句。
不过不能直接就上去写,需要参照MyBatis帮助文档中所规定的格式。
将文中代码直接复制到StudentDao.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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
代码说明:
1.指定约束文件
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
其中,【mybatis-3-mapper.dtd】是约束文件的名称,拓展名为.dtd
约束文件作用:
- 限制、检查出现在映射文件中的标签和属性,检验是否符合mybatis规范。
- 只有符合规范的标签才能被mybatis所识别。
- 这一部分的语句是固定的,不需要记忆,直接复制即可。
2.mapper:当前文件的根标签
<!--
mapper:当前文件的根标签
namespace:命名空间,自定义的唯一字符串。
要求使用Dao接口的全限定名称:com.tsccg.dao.StudentDao
-->
<mapper namespace="com.tsccg.dao.StudentDao">
<!--
select:表示查询操作
在当前文件中,可以使用特定的标签,表示对数据库的特定操作:
<select>:表示执行查询操作,在此标签中编写select语句
<update>:表示执行更新操作,在此标签中编写update语句
<insert>:表示执行插入操作,在此标签中编写insert语句
<delete>:表示执行删除操作,在此标签中编写delete语句
id:你要执行的sql语句的唯一标识,mybatis会根据这个id的值找到要执行的sql语句
id的值可以自定义,但是要求使用接口中的方法名,这是在开发中的规则
resultType:表示查询语句的返回结果类型。使用全限定类名
-->
<select id="findAll" resultType="com.tsccg.entity.Student">
<!--需要执行的sql语句-->
select * from t_student order by id asc
</select>
</mapper>
最终配置好的映射文件:
<?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.tsccg.dao.StudentDao">
<select id="findAll" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student order by id asc
</select>
</mapper>
2.7创建主配置文件
一个项目只有一个主配置文件,同样是xml格式。
主配置文件提供了数据库的连接信息和sql映射文件的位置信息。
在main目录下的resources里,创建主配置文件【mybatis.xml】
从MyBatis帮助文档中找到主配置文件的标准格式,复制到mybatis.xml里去。
主配置文件标准格式:
<?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:告诉mybatis使用哪个数据库的连接信息。
-->
<environments default="mydev">
<!-- environment:一个数据库信息的配置,环境
id:表示环境的名称,是自定义的唯一变量
-->
<environment id="mydev">
<!-- transactionManager:mybatis的事务类型
type:JDBC(表示使用JDBC中的Connection对象的commit,rollback做事务处理)
-->
<transactionManager type="JDBC"/>
<!-- dataSource:表示数据源,连接数据库的
type:表示数据源的类型,POOLED表示使用连接池
-->
<dataSource type="POOLED">
<!-- driver,url,username,password都是固定的,不能自定义 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_mybatis?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 指定sql mapper(sql映射文件)的位置
注意:如果之前在pom.xml文件里,没有添加maven插件去指定扫描src/main/java和src/main/resources目录下的xml文件,这里是找不到映射文件的
-->
<mappers>
<!-- 一个mapper标签指定一个文件的位置
从类路径(target/classes)开始的路径信息
-->
<mapper resource="com/tsccg/dao/StudentDao.xml"/>
</mappers>
</configuration>
2.8创建查询语句测试类
创建Mybatis的SqlSession对象执行sql语句
package com.tsccg;
import com.tsccg.entity.Student;
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;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/07 01:22
* 访问mybatis读取t_student表中信息
*/
public class MyApp {
public static void main(String[] args) throws IOException {
//1.定义mybatis主配置文件的名称,从类路径的根开始(target/classes)
String config = "mybatis.xml";
//2.读取这个config表示的文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//4.创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(in);
//5.【重要】获取SqlSession对象,从SqlSessionFactory中获取SqlSession
SqlSession sqlSession = factory.openSession();
//6.【重要】指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
String sqlId = "com.tsccg.dao.StudentDao.findAll";
//7.执行sql语句,通过sqlId找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
//8.输出结果
for (Student student : studentList) {
System.out.println(student);
}
//9.关闭SqlSession对象
sqlSession.close();
}
}
2.9运行测试
我用的maven是3.3.9;idea是2020.1。
运行时出现了Error:(3, 28) java: 程序包org.apache.ibatis.io不存在的错误,
如下图:
这说明maven的版本和idea不兼容。
解决方法:
打开settings,进行如下操作
重新运行:
这次虽然已经查出数据了,但是显示中文乱码。
解决方案:
设置idea server编码。在菜单栏找到”Run->Editconfigration” 在右侧设置vm option为-Dfile.encoding=utf-8
-Dfile.encoding=utf-8
重新运行程序:
还是乱码?莫慌。重新打开该项目:
运行成功。
如果仍未解决可以看看这篇博客:https://www.cnblogs.com/wcxcc/p/11545076.html
2.10打印日志
在mybatis.xml文件中加入日志配置,可以在控制台输出执行的 sql语句和参数
在指定约束条件后面添加下面语句:
<?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:控制mybatis全局行为-->
<settings>
<!--设置mybatis向控制台输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
重新运行项目,显示日志:
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building mybatis-01 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ mybatis-01 ---
//初始化,调用mybatis的日志实现类StdOutImpl
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.//使用连接池
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
//开启数据库连接
Opening JDBC Connection
//创建Connection对象
Created connection 1589683045.
//关闭自动提交事务(开启一个事务)
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//需要执行的预编译sql语句
==> Preparing: select id,name,email,age from t_student order by id asc
==> Parameters://向预编译的sql语句中添加参数(这里执行的是查询语句,故没有参数)
//查询结果
<== Columns: id, name, email, age//字段名
<== Row: 1001, 张三, zhangsan@qq.com, 20//记录
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 王五, wangwu@qq.com, 22
<== Total: 3//共得到3条记录
//我们自己写的打印查询到的3个Student对象
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='王五', email='wangwu@qq.com', age=22}
//开启事务的自动提交(关闭事务)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//关闭连接
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//将Connection对象归还给连接池,方便其他地方再次调用
Returned connection 1589683045 to pool.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.954 s
[INFO] Finished at: 2021-09-07T20:40:57+08:00
[INFO] Final Memory: 11M/153M
[INFO] ------------------------------------------------------------------------
2.11补全基本的CRUD
2.11.1在StudentDao接口中添加方法
package com.tsccg.dao;
import com.tsccg.entity.Student;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/06 21:59
* 接口操作t_student表
*/
public interface StudentDao {
/**
* 查询表中所有数据
* @return 返回一个存放Student对象的List集合
*/
List<Student> findAll();
/**
* 向表中插入记录
* @return 返回影响的记录条数
*/
int insertStudent();
/**
* 删除表中记录
* @return 返回影响条数
*/
int deleteStudent();
/**
* 更新表中纪律
* @return 返回影响条数
*/
int updateStudent();
}
2.11.2在sql映射文件里添加sql语句
<?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.tsccg.dao.StudentDao">
<!--查询操作-->
<select id="findAll" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student order by id asc
</select>
<!--插入操作-->
<insert id="insertStudent">
<!--这里的#{}指的是sql语句里的占位符【?】;id、name指的是Student实体类里的id、name属性,当插入一条记录时,传过来的是一个保存着数据的Student对象,这样可以直接读取对象里的数据-->
insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
</insert>
<!--删除操作-->
<delete id="deleteStudent">
delete from t_student where id = #{id}
</delete>
<!--更新操作-->
<update id="updateStudent">
update t_student set name = #{name} where id = #{id}
</update>
</mapper>
2.11.3编写测试方法
为了方便起见,把CRUD都搬到测试类里实现。
在test/java目录下新建【com.tsccg.TestStudentDao】
package com.tsccg;
import com.tsccg.entity.Student;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/07 17:58
* 未使用工具类的测试类
*/
public class TestStudentDao {
/**
* 获取SqlSession对象
* @return 返回SqlSession对象
* @throws IOException
*/
private static SqlSession getSqlSession() throws IOException {
String config = "mybatis.xml";
InputStream in = Resources.getResourceAsStream(config);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//获取非自动提交的SqlSession对象,从SqlSessionFactory中获取SqlSession
return factory.openSession();
}
/**
* 测试查询功能
* @throws IOException
*/
@Test
public void testFindAll() throws IOException {
//【重要】获取SqlSession对象,从SqlSessionFactory中获取SqlSession
SqlSession sqlSession = getSqlSession();
//【重要】指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
String sqlId = "com.tsccg.dao.StudentDao.findAll";
//执行sql语句,通过sqlId找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
//输出结果
for (Student student : studentList) {
System.out.println(student);
}
//关闭SqlSession对象
sqlSession.close();
}
/**
* 测试插入功能
* @throws IOException
*/
@Test
public void testInsertStudent() throws IOException {
//获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//准备需要插入的Student数据,以实体类对象为载体
Student newStudent = new Student(1004,"赵六","zhaoliu@qq.com",25);
//指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
//执行sql语句,得到处理结果
int result = sqlSession.insert(sqlId,newStudent);
//由于我们获取的是一个非自动提交的SqlSession对象,所以需要手动提交事务
sqlSession.commit();
//输出结果
System.out.println("insert执行结果:" + result);
//关闭SqlSession对象
sqlSession.close();
}
/**
* 测试删除功能
* @throws IOException
*/
@Test
public void testDeleteStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
int result = sqlSession.delete(sqlId,1004);
//提交事务
sqlSession.commit();
System.out.println("delete执行结果:" + result);
sqlSession.close();
}
/**
* 测试更新功能
* @throws IOException
*/
@Test
public void testUpdateStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
Student newStudent = new Student(1003,"张飞",null,null);
int result = sqlSession.update(sqlId,newStudent);
//提交事务
sqlSession.commit();
System.out.println("update执行结果:" + result);
sqlSession.close();
}
}
2.11.4开始测试
1.测试插入功能
2.测试删除功能
3.测试更新功能
3.MyBatis对象分析
3.1使用mybatis时用到的对象
IDEA快捷键:ctrl+h---->显示实现类和父类
3.1.1Resources类
Resources是mybatis中的一个类,负责读取主配置文件。
有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。
InputStream in = Resources.getResourceAsStream("mybatis.xml");
3.1.2SqlSessionFactoryBuilder类
其对象负责调用build()方法创建SqlSessionFactory对象。
SqlSessionFactoryBuilder对象在创建完SqlSessionFactory对象后,就完成了其使命,可以被随即销毁掉。因此,一般会将该对象创建为一个方法内的局部对象,方法一结束,对象就被销毁。
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
3.1.3SqlSessionFactory接口【重要】
SqlSessionFactory接口对象是一个重量级对象。程序创建该对象的耗时比较长,使用资源较多,是线程安全的,在整个项目中,有一个就够用了。
其职责是调用openSession()方法创建SqlSession对象。
SqlSession sqlSession = factory.openSession();
- openSession(true):创建一个有自动提交功能的SqlSession对象
- openSession(false):创建一个没有自动提交功能的SqlSession对象,执行完增删改操作后需要手动提交
- openSession():无参数时默认同openSession(false)
3.1.4SqlSession接口【重要】
SqlSession接口对象用于执行持久化操作。一个SqlSession对象对应一次数据库会话,一次会话以SqlSession对象的创建开始,以SqlSession对象的关闭结束。
SqlSession接口定义了操作数据的方法。如:selectOne()、selectList()、insert()、update()、delete()、commit()、rollback()
其实现类为:DefaultSqlSession(可以使用ctrl+h找到其实现类)
使用要求:
SqlSession接口对象不是线程安全的,在每次数据库会话结束前,需要马上调用其close()方法,将其关闭。
为了实现这点,需要在方法内部创建,使用完毕后关闭。
3.2封装Mybatis工具类
3.2.1工具类
package com.tsccg.util;
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;
/**
* @Author: TSCCG
* @Date: 2021/09/08 14:08
* MyBatis工具类
*/
public class MyBatisUtil {
private static SqlSessionFactory factory = null;
static {
//必须和主配置文件名相同
String config = "mybatis.xml";
try {
InputStream in = Resources.getResourceAsStream(config);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取非自动提交事务的SqlSession对象
* @return 返回一个SqlSession对象
*/
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
if (factory != null) {
//创建非自动提交事务的SqlSession对象
sqlSession = factory.openSession();
}
return sqlSession;
}
}
3.2.2测试类
package com.tsccg;
import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/07 17:58
* 使用工具类的测试类
*/
public class TestStudentDao2 {
/**
* 测试查询功能
*/
@Test
public void testFindAll() {
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.findAll";
List<Student> studentList = sqlSession.selectList(sqlId);
//输出结果
for (Student student : studentList) {
System.out.println(student);
}
//关闭SqlSession对象
sqlSession.close();
}
/**
* 测试插入功能
*/
@Test
public void testInsertStudent() {
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
Student newStudent = new Student(1004,"赵六","zhaoliu@qq.com",25);
String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
int result = sqlSession.insert(sqlId,newStudent);
sqlSession.commit();
//输出结果
System.out.println("insert执行结果:" + result);
//关闭SqlSession对象
sqlSession.close();
}
/**
* 测试删除功能
*/
@Test
public void testDeleteStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
int result = sqlSession.delete(sqlId,1004);
//提交事务
sqlSession.commit();
System.out.println("delete执行结果:" + result);
sqlSession.close();
}
/**
* 测试更新功能
*/
@Test
public void testUpdateStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
Student newStudent = new Student(1003,"张飞",null,null);
int result = sqlSession.update(sqlId,newStudent);
//提交事务
sqlSession.commit();
System.out.println("update执行结果:" + result);
sqlSession.close();
}
}
测试结果:
4.Mybatis使用传统方式进行Dao开发
使用传统方式就是使用Dao的实现类来操作数据库
4.1传统Dao开发方式步骤
4.1.1创建Dao接口实现类
package com.tsccg.dao.impl;
import com.tsccg.dao.StudentDao;
import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/08 16:41
* dao实现类
*/
public class StudentDaoImpl implements StudentDao {
@Override
public List<Student> selectStudents() {
//通过工具类获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//指定要执行的sql语句的标识:sql映射文件中的namespace + "." + 标签id值
String sqlId = "com.tsccg.dao.StudentDao.selectStudents";
//执行sql语句,通过sqlId找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
//关闭资源
sqlSession.close();
//将执行结果返回
return studentList;
}
@Override
public int insertStudent(Student student) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
int result = sqlSession.insert(sqlId,student);
//提交事务
sqlSession.commit();
sqlSession.close();
return result;
}
@Override
public int deleteStudent(Integer id) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
int result = sqlSession.delete(sqlId,id);
sqlSession.commit();
sqlSession.close();
return result;
}
@Override
public int updateStudent(Student student) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
int result = sqlSession.update(sqlId,student);
sqlSession.commit();
sqlSession.close();
return result;
}
}
4.1.2在sql mapper文件里编写sql语句
<?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.tsccg.dao.StudentDao">
<select id="selectStudents" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
</select>
<insert id="insertStudent">
insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
</insert>
<delete id="deleteStudent">
delete from t_student where id = #{id}
</delete>
<update id="updateStudent">
update t_student set name = #{name} where id = #{id}
</update>
</mapper>
4.1.3创建测试类
package com.tsccg;
import com.tsccg.dao.StudentDao;
import com.tsccg.dao.impl.StudentDaoImpl;
import com.tsccg.entity.Student;
import org.junit.Test;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/08 16:34
* 调用dao接口实现类进行测试
*/
public class TestStudentDao {
@Test
public void testSelectStudents() {
//创建dao接口实现类对象
StudentDao dao = new StudentDaoImpl();
//调用实现类对象执行查询方法,得到处理结果
List<Student> studentList = dao.selectStudents();
//输出查询结果
for (Student student : studentList) {
System.out.println(student);
}
}
@Test
public void testInsertStudents() {
//创建实现类对象
StudentDao dao = new StudentDaoImpl();
Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
//调用插入方法
int result = dao.insertStudent(student);
System.out.println("insert执行结果:" + result);
}
@Test
public void testDeleteStudents() {
StudentDao dao = new StudentDaoImpl();
int id = 1004;
int result = dao.deleteStudent(id);
System.out.println("delete执行结果:" + result);
}
@Test
public void testUpdateStudents() {
StudentDao dao = new StudentDaoImpl();
Student student = new Student(1004,"关羽",null,null);
int result = dao.updateStudent(student);
System.out.println("update执行结果:" + result);
}
}
4.2分析传统Dao开发方式
我们来看上面Dao接口实现类做了什么工作(以实现类的selectStudents方法为例):
@Override
public List<Student> selectStudents() {
//通过工具类获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//指定要执行的sql语句的标识:sql映射文件中的namespace + "." + 标签id值
String sqlId = "com.tsccg.dao.StudentDao.selectStudents";
//执行sql语句,通过sqlId找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
//关闭资源
sqlSession.close();
//将执行结果返回
return studentList;
}
我们可以发现,Dao的实现类并没有做什么实质性的工作,仅仅是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句。
真正对数据库进行操作的工作其实是由框架通过映射文件mapper中的SQL完成的。
<mapper namespace="com.tsccg.dao.StudentDao">
<select id="selectStudents" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
</select>
</mapper>
因此,MyBatis框架就通过动态代理的方式摒弃了Dao的实现类,直接定位到mapper映射文件中的相应sql语句,对数据库进行操作。这种对Dao接口的实现方式被称为Mapper动态代理方式。
Mapper动态代理方式不需要程序员创建Dao接口实现类,实现类由MyBatis结合映射文件通过动态代理创建的。
5.MyBatis框架Dao代理
5.1分析使用动态代理的条件
我们先来看看使用传统方式进行Dao开发时sql语句的执行方式:
//创建dao接口实现类对象
StudentDao dao = new StudentDaoImpl();
//调用实现类对象执行查询方法,得到处理结果
List<Student> studentList = dao.selectStudents();
1.dao接口对象:
- 类型:StudentDao
- 全限定名称:com.tsccg.dao.StudentDao,和mapper映射文件里的namespace属性值一致
2.调用的方法名:selectStudents,和mapper映射文件中的id属性值一致
3.通过dao接口里方法的返回值可以确定MyBatis要调用SqlSession里哪一个方法
- 如果返回值是List,调用的是SqlSession.selectList()方法
- 如果返回值是int或其他非List类型,那么可以查看mapper文件里的sql语句标签< select>,< insert>来确定方法
MyBatis的动态代理:
mybatis根据dao的方法调用,获取执行sql语句的信息,根据这些信息创建出一个dao接口的实现类,并创建这个类的对象。然后通过SqlSession对象调用相应方法,访问数据库。
5.2Mybatis使用Dao代理进行开发
5.2.1调用getMapper方法获取代理对象
只需要调用SqlSession的getMapper()方法,就可以获取指定接口的实现类对象。该对象的参数为指定Dao接口的class值。
SqlSession session = factory.openSession();
//使用mapper动态代理获取接口的代理对象
StudentDao dao = session.getMapper(StudentDao.class);
使用工具类:
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口的代理对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
5.2.2使用Dao代理对象执行sql语句
package com.tsccg;
import com.tsccg.dao.StudentDao;
import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @Author: TSCCG
* @Date: 2021/09/08 16:34
* 使用mapper动态代理
*/
public class TestStudentDao {
@Test
public void testSelectStudents() {
//调用工具类获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口的代理对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> studentList = dao.selectStudents();
for (Student student : studentList) {
System.out.println(student);
}
}
@Test
public void testInsertStudents() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
//调用插入方法
int result = dao.insertStudent(student);
//提交事务
sqlSession.commit();
System.out.println("insert执行结果:" + result);
}
@Test
public void testDeleteStudents() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int id = 1004;
int result = dao.deleteStudent(id);
//提交事务
sqlSession.commit();
System.out.println("delete执行结果:" + result);
}
@Test
public void testUpdateStudents() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student(1004,"关羽",null,null);
int result = dao.updateStudent(student);
//提交事务
sqlSession.commit();
System.out.println("update执行结果:" + result);
}
}
测试结果:
5.3深入理解参数
5.3.1parameterType
我们在使用dao代理执行sql语句时,需要从java代码把参数传递到mapper映射文件,而parameterType就是给sql语句指明传过去的参数是什么类型的。
parameterType的值是java的数据类型全限定名称或者是mybatis定义的别名。
如:parameterType="java.lang.Integer"【全限定名称】
parameterType="int"【mybatis定义的别名】
传参单位:dao代理对象
@Test
public void testDeleteStudents() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int id = 1004;//参数id
int result = dao.deleteStudent(id);//传参
//提交事务
sqlSession.commit();
System.out.println("delete执行结果:" + result);
}
接收参数单位:mapper映射文件
<mapper namespace="com.tsccg.dao.StudentDao">
<!--指明参数类型是java.lang.Integer-->
<delete id="deleteStudent" parameterType="java.lang.Integer">
delete from t_student where id = #{id}
</delete>
</mapper>
注意:这个属性是可选的,因为mybatis通过反射机制能够自动发现接口参数的数据类型,所以这个属性可以没有,我们一般在开发中也不写。
5.3.2只有一个简单类型参数
简单类型参数:mybatis把String和java基本数据类型(int)以及对应的引用数据类型(Integer)都叫做简单类型。
当Dao接口中方法的参数列表只有一个且为简单类型时,在mapper文件里获取简单类型的参数值,使用:
也就是说#{}里的名称可以是任意的,无论与简单类型参数/是否相同,都能拿到参数值。
//接口中方法:
int deleteStudent(Integer id);
//mapper文件:
<delete id="deleteStudent">
delete from t_student where id = #{studentId}
</delete>
//测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int id = 1004;
int result = dao.deleteStudent(id);
5.3.3mybatis是封装的jdbc操作
使用#{}后,mybatis执行sql是使用jdbc里的PreparedStatement对象。
由mybatis执行下面的代码:(以select为例)
1.mybatis创建Connection,PreparedStatement对象
//#{}就相当于占位符”?“
String sql = "select id,name,email,age from t_student where id = ?";
PreparedStatement ps = conn.preparedStatement(sql);
ps.setInt(1,1004);
2.执行sql,封装为resultType="com.tsccg.entity.Student"这个对象
ResultSet rs = ps.executeQuery();
Student student = null;
while(rs.next()) {
student = new Student(rs.getInt("id"),rs.getString("name"),rs.getEmail("email"),rs.getInt("age"));
}
return student;//将结果返回
5.3.4多个参数-使用@Param【常用】
当sql语句需要传入多个参数时,如果直接把参数传过去,mapper映射文件会无法识别。
Dao接口方法:
int updateStudent(Integer id,String name);
mapper文件:
<update id="updateStudent">
update t_student set name = #{name} where id = #{id}
</update>
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateStudent(1004,"刘备");//直接传递两个简单类型参数
mapper文件无法识别参数:
为了让mapper文件能够识别传过去的参数,我们可以使用@Param("自定义参数")的方式命名参数。
Dao接口方法:
int updateStudent(@Param("gId") Integer id, @Param("newName") String name);
mapper文件:
<!--多个参数,使用@Param命名参数-->
<update id="updateStudent">
update t_student set name = #{newName} where id = #{gId}
</update>
dao代理传参:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateStudent(1004,"刘备");
mepper可以识别参数:
5.3.5多个参数-使用对象【常用】
使用java对象也可以传递参数,java的属性值就是sql需要的参数值。每一个属性就是一个参数。
语法格式:
#{属性名,javaType=java中数据类型名,jdbcType=数据中类型名}
如:
Dao接口方法:
int insertStudent(Student student);
mapper文件:
<insert id="insertStudent">
insert into t_student(id,name,email,age) value (
#{id,javaType=java.lang.Integer,jdbcType=INTEGER},
#{name,javaType=java.lang.String,jdbcType=VARCHAR},
#{email,javaType=java.lang.String,jdbcType=VARCHAR},
#{age,javaType=java.lang.Integer,jdbcType=INTEGER}
)
</insert>
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
int result = dao.insertStudent(student);
当然,如此繁琐的代码一定会有简化方式的。
简化后格式:
#{属性名}
其中,javaType和jdbType的值,mybatis可以通过反射获取,不需要手动提供。
<insert id="insertStudent">
insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
</insert>
5.3.6多个参数-按位置传参【少用】
按位置传参语法:
#{arg位置}
参数位置从0开始,第一个参数是#{arg0},第二个参数是#{arg1}
注意:mybatis-3.3版本和之前的版本使用#{0},#{1}方式,从3.4版本开始使用#{arg0}方式
Dao接口方法:
int updateEmailStudent(Integer id,String email);
mapper文件:
<!--按位置-->
<update id="updateEmailStudent">
update t_student set email = #{arg1} where id = #{arg0}
</update>
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateEmailStudent(1004,"liubei@qq.com");
当方法定义参数顺序出错时,此方式就会出错,故在开发中使用较少。
5.3.7Map传参【少用】
Map集合可以存储多个值,使用Map向mapper文件一次传入多个参数。
Map集合使用String的key,Object类型的值存储参数。mapper文件使用#{key}引用参数值。
Dao接口方法:
int updateAgeStudent(Map<String,Object> map);
mapper文件:
<!--使用Map传参-->
<update id="updateAgeStudent">
<!--通过key引用参数值-->
update t_student set age = #{stuAge} where id = #{stuId}
</update>
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<String,Object> data = new HashMap<>();
data.put("stuId",1004);
data.put("stuAge",22);
int result = dao.updateAgeStudent(data);
这种方式不推荐使用,理由如下:
- 使用Map传参维护成本高。一旦传入的key值发生改变,那么在引用参数时,也必须跟着改变;
- 使用Map作为参数可读性差。无法从“Map<String,Object> map”中明确参数值是什么类型,有几个参数值。
5.3.8#和$的区别
:占位符:
- 告诉mybatis使用实际的参数值代替所在位置
- 使用PreparedStatement对象执行sql语句,用#{...}代替sql语句的“?”
- 这样做可以防止sql注入,我们通常使用的都是这个
$:字符串替换:
- 告诉mybatis使用$包含的字符串替换所在位置
- 使用Statement对象执行sql语句,把sql语句和${}的内容拼接起来
- 不能防止sql注入,主要用于替换表名,列名,不同列排序等操作
我们打开日志输出:
<settings>
<!--设置mybatis向控制台输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
1.使用#:
select id,name,email,age from t_student where name = #{studentName}
打印日志:
==> Preparing: select id,name,email,age from t_student where name = ?
==> Parameters: 张三(String)
2.使用$:
select id,name,email,age from t_student where name = ${studentName}
打印日志:
==> Preparing: select id,name,email,age from t_student where name = '张三'
==> Parameters:
5.3.9对$修饰的语句进行sql注入
Dao接口方法:
Student selectStudentUse$(@Param("studentName") String name);
mapper文件:
<!--使用$-->
<select id="selectStudentUse$" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where name = ${studentName}
</select>
测试方法:
@Test
public void testSelectStudentsUse$() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口实现类对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
//sql注入:【'' or id = 1001】或者【"'';drop table t_student;"】
Student student = dao.selectStudentUse$("'' or id = 1001");
System.out.println(student);
}
查看日志:
==> Preparing: select id,name,email,age from t_student where name = '' or id = 1004
==> Parameters:
<== Columns: id, name, email, age
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 1
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
5.3.10使用$替换列名
Dao接口方法:
List<Student> selectUse$Order(@Param("colName") String colName);
mapper文件:
<select id="selectUse$Order" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student order by ${colName}
</select>
测试方法:
@Test
public void testSelectUse$Order() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
//替换列名
List<Student> studentList = dao.selectUse$Order("name");
for (Student student : studentList) {
System.out.println(student);
}
}
查看日志:
==> Preparing: select id,name,email,age from t_student order by name
==> Parameters:
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 4
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
5.4封装MyBatis输出结果
5.4.1resultType
1.resultType是什么
resultType:指定执行sql得到的ResultSet所转换的类型,使用类型的全限定名或别名。
注意:如果返回的是一个集合,那么应该设置resultType为集合里所包含的类型,而不是集合本身。
mapper文件:
<!-- sql执行后的列的数据转换为java对象Student -->
<select id="selectStudents" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
</select>
jdbc处理查询结果集:同样的操作
ResultSet rs = stmt.executeQuery("select * from t_student");
List<Student> studentList = new ArrayList<>();
while(rs.next()) {
Student student = new Student(stmt.getInt("id"),stmt.getString("name"),stmt.getEmail("email"),stmt.getInt("age"));
//将从数据库中拿到的数据装进Student对象,封装进List集合
studentList.add(student);
}
return studentList;//将结果返回
2.返回值是简单类型
接口方法:
public interface StudentDao {
//统计学生数量,返回类型:简单类型
int countStudent();
}
mapper文件:
<mapper namespace="com.tsccg.dao.StudentDao">
<!-- 结果为简单类型 -->
<!--<select id="countStudent" resultType="java.lang.Integer">--><!--全限定名-->
<select id="countStudent" resultType="int"><!--别名-->
select count(*) from t_student;
</select>
</mapper>
测试类:
public class TestStudentDao {
@Test
public void testCountStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
//执行接口方法并接收返回值
int count = dao.countStudent();
System.out.println("学生数量:" + count);
}
}
3.返回值是对象类型
处理方式:
- mybatis执行sql语句,然后mybatis调用类的有参/无参构造方法,创建java对象。
- mybatis把ResultSet指定列值赋给同名的对象属性。
注意:如果ResultSet指定列值和对象属性名不一致,则无法将数据写入对象中。
接口方法:
public interface StudentDao {
//查询一条学生信息,返回类型:对象类型
Student selectOneStudent(Integer id);
}
mapper文件:
<mapper namespace="com.tsccg.dao.StudentDao">
<!-- -->
<!--查询一个学生,结果为对象类型-->
<select id="selectOneStudent" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where id = #{id};
</select>
</mapper>
测试类:
public class TestStudentDao {
@Test
public void testSelectStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口实现类对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<Object,Object> map = dao.selectStudent(1003);
System.out.println(map);
}
}
日志报告:
==> Preparing: select id,name,email,age from t_student where id = ?;
==> Parameters: 1004(Integer)
<== Columns: id, name, email, age
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 1
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
resultType指明的对象类型是任意的,只要能把查询出的结果装进一个对象就行,不用非得是实体类类型。
4.返回值是Map类型
sql的查询结果作为Map的key和value,推荐使用Map<Object,Object>。
注意:Map作为接口返回值,sql语句的查询结果最多只能有一条记录,大于一条记录会报错。
接口方法:
public interface StudentDao {
//返回类型:Map
Map<Object,Object> selectStudent(Integer id);
}
mapper文件:
<!--结果类型为Map-->
<select id="selectStudent" resultType="java.util.HashMap">
select id,name,email,age from t_student where id = #{id};
</select>
测试方法:
@Test
public void testSelectStudents() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口实现类对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<Object,Object> map = dao.selectStudent(1003);
System.out.println(map);
}
日志报告:
==> Preparing: select id,name,email,age from t_student where id = ?;
==> Parameters: 1003(Integer)
<== Columns: id, name, email, age
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 1
{name=张飞, id=1003, email=wangwu@qq.com, age=22}
5.4.2resultMap
结果映射,指定列名和java对象的属性对应关系。
- 可以自定义列值赋给一个指定的属性
- 当列名和属性名不一样时,一定要使用resultMap,不然无法将查询结果写入对象中
使用resultMap步骤:
- 先定义resultMap格式
- 在select标签内,使用resultMap属性引用1所定义的格式
接口方法:
public interface StudentDao {
//resultMap
List<Student> selectAllStudent();
}
mapper文件:
<!-- 定义resultMap
id: 自定义名,作为你定义的这个resultMap的标识
type:使用的java类型的全限定名
-->
<resultMap id="myResultMap" type="com.tsccg.entity.Student">
<!--列名和java属性的关系-->
<!--主键列,使用id标签
column:数据库表列名
property:java对象属性名
-->
<id column="id" property="id"/>
<!--非主键列,使用result标签
column:数据库表列名
property:java对象属性名
-->
<result column="name" property="email"/><!--将数据库表中的name赋给java对象的email属性-->
<result column="email" property="name"/><!--数据库表中的email赋给java对象的name属性-->
<result column="age" property="age"/>
</resultMap>
<!--使用resultMap-->
<select id="selectAllStudent" resultMap="myResultMap">
select id,name,email,age from t_student
</select>
<!--定义的resultMap可以复用-->
<select id="selectOneStudent" resultMap="myResultMap">
select id,name,email,age from t_student where id = #{id};
</select>
测试方法:
@Test
public void testSelectAllStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口实现类对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> studentList = dao.selectAllStudent();
for (Student student : studentList) {
System.out.println(student);
}
}
查看日志:
==> Preparing: select id,name,email,age from t_student
==> Parameters:
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 4
Student{id=1001, name='zhangsan@qq.com', email='张三', age=20}
Student{id=1002, name='lisi@qq.com', email='李四', age=21}
Student{id=1003, name='wangwu@qq.com', email='张飞', age=22}
Student{id=1004, name='liubei@qq.com', email='赵六', age=22}
从日志可见,数据库表中的name被赋给了java对象的email属性,数据库表中的email被赋给了java对象的name属性。
resultMap常用来处理表中列名和java对象属性名不一致时的情况。
比如说现在有一个实体类:StudentPlus,其属性名与列名不一致:
public class StudentPlus {
//定义属性名,和t_student表的列名不一致
private Integer studentId;
private String studentName;
private String studentEmail;
private Integer studentAge;
...
}
现使用该类作为返回结果。
接口方法:
public interface StudentDao {
List<StudentPlus> selectAllStudent2();
}
mapper文件:
<!-- 定义resultMap,指定列名赋给对象中哪一个属性 -->
<resultMap id="myResultMap" type="com.tsccg.entity.StudentPlus">
<!--列名和java属性的关系-->
<id column="id" property="studentId"/>
<!--非主键列,使用result标签-->
<result column="name" property="studentName"/>
<result column="email" property="studentEmail"/>
<result column="age" property="studentAge"/>
</resultMap>
<!--使用resultMap-->
<select id="selectAllStudent2" resultMap="myResultMap">
select id,name,email,age from t_student
</select>
测试方法:
@Test
public void testSelectAllStudent2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<StudentPlus> studentList = dao.selectAllStudent2();
for (StudentPlus student : studentList) {
System.out.println(student);
}
}
查看日志:
==> Preparing: select id,name,email,age from t_student
==> Parameters:
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 4
StudentPlus{studentId=1001, studentName='张三', studentEmail='zhangsan@qq.com', studentAge=20}
StudentPlus{studentId=1002, studentName='李四', studentEmail='lisi@qq.com', studentAge=21}
StudentPlus{studentId=1003, studentName='张飞', studentEmail='wangwu@qq.com', studentAge=22}
StudentPlus{studentId=1004, studentName='赵六', studentEmail='liubei@qq.com', studentAge=22}
当然,解决数据库表列名和java对象属性名不一致的方法还有很多。
比如:在mapper文件中执行sql语句时,添加别名:
select id as studentId,name as studentName from t_student;
5.5模糊查询like
有两种方式实现:
1.将“%”作为查询数据的一部分,从java代码传到mapper文件中。
2.在mapper文件中的sql语句里提前写好“%”
5.5.1第一种方式:%作为参数
接口方法:
public interface StudentDao {
//模糊查询1:java代码传参--->%张%
List<Student> selectLikeOne(String name);
}
mapper文件:
<!--第一种方式实现模糊查询:java代码传参%-->
<select id="selectLikeOne" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where name like #{name}
</select>
测试方法:
//第一种
@Test
public void testSelectLikeOne() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
String name = "%张%";
List<Student> studentList = dao.selectLikeOne(name);
for (Student student : studentList) {
System.out.println(student);
}
}
查看执行日志:
==> Preparing: select id,name,email,age from t_student where name like ?
==> Parameters: %张%(String)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
5.5.2第二种方式:提前写好%
接口方法:
public interface StudentDao {
//模糊查询2:提前写好%
List<Student> selectLikeTwo(String name);
}
mapper文件:
<!--第二种方式实现模糊查询:提前写好%-->
<select id="selectLikeTwo" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where name like "%" #{name} "%"
</select>
测试方法:
//第二种like
@Test
public void testSelectLikeTwo() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
String name = "张";
List<Student> studentList = dao.selectLikeTwo(name);
for (Student student : studentList) {
System.out.println(student);
}
}
查看执行日志:
==> Preparing: select id,name,email,age from t_student where name like "%" ? "%"
==> Parameters: 张(String)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
6.动态sql
6.1动态sql的概念
动态sql就是通过Mybatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。
常用的动态SQL标签有:<if >、<where >、<choose/ >、<foreach >等。
6.2动态sql适用的情况
动态sql主要用于解决查询条件不确定的情况。
在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,需要执行的sql语句也不同。
如果将每种可能的情况都列出来,对所有条件进行排列组合,会出现大量的sql语句。
此时,就可以使用动态sql来解决这样的问题。
6.3动态sql之<if >
<if >是判断条件的。对于该标签的执行,当test的值为true时,会将其包含的sql片段拼接到其所在的SQL语句中。
语法:
<select id="selectIf" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
where 1 = 1<!--添加(1=1)是为了避免拼接sql语句时出现语法错误-->
<if test="判断java对象的属性值">
sql片段
</if>
</select>
dao接口方法:
public interface StudentDao {
/**
* 动态sql--if
* 参数必须为java对象
*/
List<Student> selectIf(Student student);
}
mapper文件:
<select id="selectIf" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
where 1 = 1
<if test="name != null and name != ''">
and name like #{name}
</if>
<if test="age > 0">
<!--在 mapper的动态SQL中若出现大于号(>)、小于号(<)等,
最好将其转换为实体符号。否则,XML可能会出现解析出错问题。
(<)对应(<)
(>)对应(>)
(>=)对应(>=)
(<=)对应(<=)
-->
and age > #{age}
</if>
</select>
测试方法:
@Test
public void testSelectIf() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setName("%张%");
student.setAge(20);
List<Student> studentList = dao.selectIf(student);
for(Student student1 : studentList) {
System.out.println(student1);
}
}
执行日志:
==> Preparing: select id,name,email,age from t_student where 1 = 1 and name like ? and age > ?
==> Parameters: %张%(String), 20(Integer)
<== Columns: id, name, email, age
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 1
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
去掉测试方法中的student.setAge(20);再次执行:
==> Preparing: select id,name,email,age from t_student where 1 = 1 and name like ?
==> Parameters: %张%(String)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
6.4动态sql之<where >
<where >标签用来包含多个<if >标签。当有任意if标签成立时,<where >会自动增加一个关键字,并去掉if中多余的and、or等。
接口方法:
public interface StudentDao {
/**
* 动态sql--where
* @param student 参数必须为java对象
* @return 返回一个List集合
*/
List<Student> selectWhere(Student student);
}
mapper文件:
<select id="selectWhere" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
<where>
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age > 0">
or age > #{age}
</if>
</where>
</select>
测试方法:
@Test
public void testSelectWhere() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setName("%张%");
student.setAge(20);
List<Student> studentList = dao.selectWhere(student);
for(Student student1 : studentList) {
System.out.println(student1);
}
}
当name和age都符合if标签条件时,查看执行日志:
==> Preparing: select id,name,email,age from t_student WHERE name = ? or age > ? //自动添加WHERE
==> Parameters: %张%(String), 20(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 3
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
当只有age符合if标签条件时,查看执行日志:
==> Preparing: select id,name,email,age from t_student WHERE age > ? //age前的or被删除了
==> Parameters: 20(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 3
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
当所有的if标签条件都为false时,查看执行日志:
==> Preparing: select id,name,email,age from t_student //没有添加where
==> Parameters:
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Row: 1004, 赵六, liubei@qq.com, 22
<== Total: 4
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}
6.5动态sql之<foreach >
6.5.1作用?格式?
当参数为数组或集合时,可以用<foreach >标签进行遍历。
语法:
<foreach collection="集合类型" item="集合中的成员" open="开始的字符" close="结束的字符" separator="集合成员间的分隔符">
#{集合中的成员}
</foreach>
collection:表示接口中的方法参数的类型,如list、array等
item:自定义的,表示数组和集合成员的变量
open、close:表示循环开始时和结束时需要拼入的字符,如"(" 和 ")"
separator:集合成员间的分隔符,如:","
相当于下面的java命令:
List<Integer> idList = new ArrayList<>();
idList.add(1001);
idList.add(1002);
idList.add(1003);
String sql = "select id,name,email,age from t_student where id in";
//把idList集合对象中的数据拼接为in格式字符串-->(1001,1002,1003)
StringBuilder strB = new StringBuilder();
strB.append("(");//开始时需要拼接的字符
for(Integer item:idList) {
//拼接数据和逗号
strB.append(item).append(",");
}
strB.deleteCharAt(strB.length()-1);//删除最后一个逗号
strB.append(")");//结束时需要拼接的字符
sql += strB;
//select id,name,email,age from t_student where id in(1001,1002,1003)
System.out.println(sql);
6.5.2遍历List<简单类型>
集合的内置类型为简单类型如:List<Integer >
需求:查询id为1001,1002,1003的学生
接口方法:
public interface StudentDao {
List<Student> selectForeachOne(List<Integer> idList);
}
mapper文件:
<!--动态sql foreach List<Integer> -->
<select id="selectForeachOne" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
<if test="list != null and list.size > 0"><!--list.size表示集合长度-->
where id in
<foreach collection="list" open="(" close=")" item="myId" separator=",">
#{myId}
</foreach>
</if>
</select>
测试方法:
@Test
public void testSelectForeach() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Integer> idList = new ArrayList<>();
idList.add(1001);
idList.add(1002);
idList.add(1003);
List<Student> studentList = dao.selectForeachOne(idList);
for (Student student : studentList) {
System.out.println(student);
}
}
查看执行日志:
==> Preparing: select id,name,email,age from t_student where id in ( ? , ? , ? )
==> Parameters: 1001(Integer), 1002(Integer), 1003(Integer)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 3
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
6.5.3遍历List<java对象>
集合的内置类型为简单类型如:List<Student >
需求:查询id为1001,1002,1003的学生
接口方法:
public interface StudentDao {
List<Student> selectForeachTwo(List<Student> stuList);
}
mapper文件:
<!--动态sql foreach 2.List<Student> -->
<select id="selectForeachTwo" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student
<if test="list != null and list.size > 0"><!--list.size表示集合长度-->
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}<!--调用对象属性-->
</foreach>
</if>
</select>
测试方法:
@Test
public void testSelectForeachTwo() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> stuList = new ArrayList<>();
Student s1 = new Student();
s1.setId(1001);
stuList.add(s1);
s1 = new Student();//指定新对象地址
s1.setId(1002);
stuList.add(s1);
s1 = new Student();
s1.setId(1003);
stuList.add(s1);
List<Student> studentList = dao.selectForeachTwo(stuList);
for (Student student : studentList) {
System.out.println(student);
}
}
查看执行日志:
==> Preparing: select id,name,email,age from t_student where id in ( ? , ? , ? )
==> Parameters: 1001(Integer), 1002(Integer), 1003(Integer)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Row: 1002, 李四, lisi@qq.com, 21
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 3
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
6.6动态sql之代码片段
<sql />标签用于定义sql片段,以便其他标签复用。
格式:
1.定义sql片段
<sql id="自定义名称">
select id,name,email,age from t_student
</sql>
2.使用sql片段
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
<include refid="id的值"/> where id=#{id}
</select>
演示:
dao接口方法:
Student selectStudentById(Integer id);
mapper文件:
<!--定义sql代码片段-->
<sql id="selectAllStudent">
select id,name,email,age from t_student
</sql>
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
<!--使用sql代码片段-->
<include refid="selectAllStudent"/> where id = #{id}
</select>
测试方法:
@Test
public void testSelectStudentById() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = mapper.selectStudentById(1002);
System.out.println(student);
}
查看执行日志:
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
7.MyBatis缓存机制
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地进行配置和定制。缓存可以极大地提升查询效率。
Mybatis中默认定义了两级缓存
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也被称为本地缓存)开启。
- 二级缓存(namespace级别的缓存,也被称为全局缓存)需要手动开启和配置。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
7.1一级缓存
一级缓存(本地缓存):SqlSession级别的缓存
1.在与数据库同一次会话期间,查询到的数据会放到本地缓存中,如果之后需要获取相同的数据,会直接从缓存中拿,没必要再去查询数据库。
2.一级缓存是一直开启的
3.一级缓存其实就是SqlSession级别的一个Map。在本次会话期间,查询到的所有结果都会放进这个Map里。
7.1.1演示一级缓存
dao接口方法:
public interface StudentDao {
Student selectStudentById(Integer id);
}
mapper文件:
<mapper namespace="com.tsccg.dao.StudentDao">
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where id = #{id}
</select>
</mapper>
测试方法:
/**
* 演示一级缓存
* SqlSession相同,查询条件相同
*/
@Test
public void testSelectStudentById1() {
//使用工具类获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//第一次查询
Student student1 = mapper.selectStudentById(1002);
System.out.println(student1);
//第二次查询:使用同一个SqlSession创建的代理对象查询同一条记录
Student student2 = mapper.selectStudentById(1002);
System.out.println(student2);
//比较两次查询得到的结果对象是否一致
System.out.println(student1 == student2);//true
sqlSession.close();
}
查看执行日志:
//开启会话
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
true
//关闭会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
从日志可见,两次查询只执行了一条sql语句,而且两次查询得到的结果对象是同一个
7.1.2一级缓存失效的四种情况
一级缓存失效有四种情况:
- SqlSession对象不同
- SqlSession对象相同,查询条件不同(当前一级缓存中还没有这条数据)
- SqlSession对象相同,查询条件相同,在两次查询期间,有增删改操作(可能会对当前数据有影响)
- 在两次查询期间,手动清除了缓存
失效情况1:SqlSession对象不同
测试方法:
/**
* 失效情况1:查询条件相同,SqlSession对象不同
*/
@Test
public void testSelectStudentById2() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();//创建第一个SqlSession对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student1 = mapper.selectStudentById(1002);//查询1002
System.out.println(student1);
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();//创建第二个SqlSession对象
StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
Student student2 = mapper2.selectStudentById(1002);//同样查询1002
System.out.println(student2);
System.out.println(student1 == student2);//false
sqlSession.close();
sqlSession2.close();
}
查看执行日志:
//开启会话1
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//开启会话2
Opening JDBC Connection
Created connection 1159114532.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
false
//关闭所有会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
Returned connection 1159114532 to pool.
由日志可见,两次查询执行了两条sql语句,得到的结果对象也不一样。
失效情况2:查询条件不同
测试方法:
/**
* 失效情况2:SqlSession对象相同,查询条件不同
*/
@Test
public void testSelectStudentById3() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student1 = mapper.selectStudentById(1002);
System.out.println(student1);
Student student2 = mapper.selectStudentById(1003);//改变查询条件
System.out.println(student2);
System.out.println(student1 == student2);
sqlSession.close();
}
查看执行日志:
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1003(Integer)
<== Columns: id, name, email, age
<== Row: 1003, 张飞, wangwu@qq.com, 22
<== Total: 1
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
false
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
失效情况3:期间有增删改操作
在dao接口中,添加修改方法
public interface StudentDao {
/**
* 查询操作
*/
Student selectStudentById(Integer id);
/**
* 修改数据
*/
int updateStudent(@Param("id") Integer id,@Param("age") Integer age);
}
mapper文件:
<mapper namespace="com.tsccg.dao.StudentDao">
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where id = #{id}
</select>
<update id="updateStudent">
update t_student set age = #{age} where id = #{id}
</update>
</mapper>
测试方法:
/**
* 失效情况3:SqlSession对象相同,查询条件同相同.但在两次查询中,有增删改操作
*/
@Test
public void testSelectStudentById4() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//第一次查询
Student student1 = mapper.selectStudentById(1002);
System.out.println(student1);
//执行修改操作
int result = mapper.updateStudent(1004,25);
System.out.println(result > 0 ? "更新成功":"更新失败");
//第二次查询
Student student2 = mapper.selectStudentById(1002);
System.out.println(student2);
System.out.println(student1 == student2);
sqlSession.close();
}
查看执行日志:
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
//第一次查询
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//执行修改操作
==> Preparing: update t_student set age = ? where id = ?
==> Parameters: 25(Integer), 1004(Integer)
<== Updates: 1
更新成功
//第二次查询
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
false
Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
失效情况4:期间手动清除缓存
测试方法:
/**
* 失效情况4:SqlSession对象相同,查询条件同相同;期间,手动清除缓存
*/
@Test
public void testSelectStudentById5() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student1 = mapper.selectStudentById(1002);
System.out.println(student1);
sqlSession.clearCache();//手动清除缓存
Student student2 = mapper.selectStudentById(1002);
System.out.println(student2);
System.out.println(student1 == student2);
sqlSession.close();
}
查看执行日志:
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
//第一次查询
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//第二次查询
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1002(Integer)
<== Columns: id, name, email, age
<== Row: 1002, 李四, lisi@qq.com, 21
<== Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
false//结果对象不同
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
7.2二级缓存
二级缓存(全局缓存):基于namespace级别的缓存
-
一个namespace对应一个二级缓存
-
工作机制
- 在一个会话中,查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭或者提交,一级缓存中的数据会被保存到二级缓存中
- 当新的会话查询同样的数据时,就可以直接拿二级缓存中存储的数据
-
不同的namespace查出的数据会放在自己对应的缓存(Map)中
7.2.1使用二级缓存步骤
1.开启全局二级缓存配置
二级缓存默认是开启的,在官方文档中就有写出:
但是我们还是要显式地指定每一个我们需要更改的配置的值,防止版本更新带来的问题。
在主配置文件中添加如下语句:
<settings>
<!--cacheEnable:全局性地开启和关闭所有mapper文件中已配置地任何缓存。默认为true-->
<setting name="cacheEnabled" value="true"/>
</settings>
2.在mapper文件中配置使用二级缓存
添加<cache />标签即可,也可以根据参数自定义配置。
<?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.tsccg.dao.StudentDao">
<!-- 配置二级缓存 -->
<!--
eviction:缓存的回收策略:
LRU(默认)-最近最少使用的:移除最长时间不被使用的对象
FIFO-先进先出:按对象进入缓存的顺序来移除它们
SOFT-软引用:移除基于垃圾收集器状态和弱引用规则的对象
WEAK-弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval:缓存刷新间隔。指定多长时间清空一次缓存,按毫秒计。默认不刷新。
readOnly:是否只读
true:只读。mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
1.因此,mybatsi为了加快获取数据速度,直接将数据在缓存中的引用交给用户。
2.这种方式虽然速度快,但是不安全。
false:非只读(默认)。mybatis认为获取的数据可能会被修改。
1.mybatis为了数据安全,会利用序列化和反序列化技术克隆一份新的数据给你。
2.这种方式虽然速度慢,但是安全。
size:指定缓存中能放多少个元素
type:指定自定义缓存的全类名。自定义缓存只需要实现Cache接口即可。
-->
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024">
</cache>
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
select id,name,email,age from t_student where id = #{id}
</select>
<update id="updateStudent">
update t_student set age = #{age} where id = #{id}
</update>
</mapper>
3.在POJO(实体类)里实现序列化接口
public class Student implements Serializable {
//指定序列化版本号
private static final long serialVersionUID = 1L;
//定义属性名,要求和t_student表的列名一致
private Integer id;
private String name;
private String email;
private Integer age;
public Student() {
}
...
}
4.演示二级缓存
编写测试方法:
- 分别创建两个SqlSession对象,同时使用两个SqlSession对象分别生成StudentDao接口的代理对象。
- 使用第一个代理对象查询学号为1001的学生信息,得到一个Student对象,然后关闭第一个SqlSession对象,也就是关闭第一个会话。
- 然后使用第二个代理对象再次查询学号为1001的学生信息,同样得到一个Student对象。
- 查看执行日志。
/**
* 演示二级缓存
*/
@Test
public void testSecondLevelCache1() {
//创建第一个SqlSession对象
SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
//创建第二个SqlSession对象
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
//执行第一次查询
Student student1 = mapper1.selectStudentById(1001);
System.out.println(student1);
sqlSession1.close();//关闭第一个会话
//执行第二次查询
Student student2 = mapper2.selectStudentById(1001);//查询相同记录
System.out.println(student2);
System.out.println(student1 == student2);
sqlSession2.close();//关闭第二个会话
}
执行日志:
//Cache Hit Ratio:缓存命中率。0.0表示在一级缓存和二级缓存中都没有找到对应数据
Cache Hit Ratio [com.tsccg.dao.StudentDao]: 0.0
//开启会话
Opening JDBC Connection
Created connection 1282811396.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
//第一次查询
==> Preparing: select id,name,email,age from t_student where id = ?
==> Parameters: 1001(Integer)
<== Columns: id, name, email, age
<== Row: 1001, 张三, zhangsan@qq.com, 20
<== Total: 1
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
//关闭会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
Returned connection 1282811396 to pool.
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
//Cache Hit Ratio:缓存命中率。0.5表示第二次查缓存有一次命中,拿到了Student对象
Cache Hit Ratio [com.tsccg.dao.StudentDao]: 0.5
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
false
注意:
- 查到的数据默认先放到一级缓存中,只有当会话提交或关闭时,才会把一级缓存中的数据放到二级缓存中。
- 一个mapper对应一个二级缓存。只有在mapper文件里写了cache标签,这个mapper才会有二级缓存。
7.2.2和缓存有关的设置
1.主配置文件里的:<setting name="cacheEnabled" value="true/false" />
true:开启全局缓存
false:关闭缓存(只关二级缓存,一级缓存一直可用)
2.每个select标签都有useCache="true/false"属性
如果为false,则表明不使用缓存(一级缓存仍使用,二级缓存不使用)
3.增删查改标签中的属性flushCache="true/false"
true:增删查改执行完后清空缓存(一二级缓存全部清空)
false:不会清空缓存
4.sqlSession.clearCache();
只清除当前会话的一级缓存
5.localCacheScope:本地缓存作用域
setting中的name属性
MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。
默认值为 SESSION,会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存(禁用一级缓存)。
8.Mybatis常用配置
8.1配置数据库属性配置文件
为了便于修改、保存、处理多个数据库的信息,可以把数据库连接信息从mybatis主配置文件中单独放到一个配置文件中。
实现步骤:
1.在main目录下的resources目录里创建一个属性配置文件jdbc.properties
在jdbc.properties文件中编写连接数据库信息,格式为key=value
#key值使用三级名称,可以做多级目录
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/db_mybatis?ullNamePatternMatchesAll=true&serverTimezone=GMT%2b8?useUnicode=true&useSSL=false&characterEncoding=utf8
jdbc.mysql.username=root
jdbc.mysql.password=123456
2.在mybatis的主配置文件中读取配置文件信息
- 在configuration标签的第一行,使用properties标签的resource属性指定数据库属性配置文件的位置。
- 然后在下方dataSource的property标签内,使用${key}的方式读取指定属性值
<configuration>
<!--指定jdbc配置文件-->
<properties resource="jdbc.properties">
</properties>
<environments default="mydev">
<environment id="mydev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.mysql.driver}"/>
<property name="url" value="${jdbc.mysql.url}"/>
<property name="username" value="${jdbc.mysql.username}"/>
<property name="password" value="${jdbc.mysql.password}"/>
</dataSource>
</environment>
</environments>
<!-- 指定sql mapper(sql映射文件)的位置-->
<mappers>
<mapper resource="com/tsccg/dao/StudentDao.xml"/>
</mappers>
</configuration>
8.2typeAliases(自定义类型别名)
MyBatis支持默认别名,也支持自定义别名。
别名主要使用在<select resultType="类型别名" >
实现步骤:
1.在主配置文件里定义别名
有两种方式定义,二选一。
<typeAliases>
<!-- 1.定义单个类型的别名 type:全限定名 alias:自定义别名 -->
<typeAlias type="com.tsccg.entity.Student" alias="myStudent"/>
<!-- 2.批量定义别名,扫描整个包下的类。
name:包名;别名为类名
-->
<package name="com.tsccg.entity"/>
</typeAliases>
2.在mapper文件里使用别名表示类型
<!--使用单个定义的别名-->
<select id="selectStudentById" resultType="myStudent">
select id,name,email,age from t_student where id = #{id}
</select>
---------------------------------------------------------------
<!--使用批量定义的别名-->
<select id="selectStudentById" resultType="Student">
select id,name,email,age from t_student where id = #{id}
</select>
8.3批量加载mapper文件
在主配置文件中,有两种方式加载mapper文件,二选一:
<!-- 指定mapper文件(sql映射文件)的位置-->
<mappers>
<!--1.逐个加载mapper文件 -->
<mapper resource="com/tsccg/dao/StudentDao.xml"/>
<mapper resource="com/tsccg/dao/StudentPlusDao.xml"/>
<!--
2.批量加载com.tsccg.dao目录下的所有mapper文件
使用package的要求:
1.mapper文件要和dao接口名一致
2.mapper文件要和dao接口位于同一目录下
-->
<package name="com.tsccg.dao"/>
</mappers>
</configuration>