目录
- Mybatis 介绍
- ORM 介绍
- 什么是 Mybatis ?
- 原生态 JDBC 操作的分析
- Mybatis 框架原理
- MyBatis API
- Resources(加载资源的工具类)
- SqlSessionFactoryBuilder(构建器)
- SqlSessionFactory(工厂对象)
- SqlSession 会话对象
- Mybatis 入门案例
- 环境搭建
- Mybatis 全局配置文件
- Mybatis 映射配置文件
- 测试代码
- 输入/输出映射详解
- 输入映射:parameterType
- 输出映射
- resultType
- resultMap
- Mybatis Mapper 代理开发
- 传统 Dao 开发方式的问题
- Mapper 代理的开发方式
- 动态代理方式源码分析
- 动态 SQL
- if、where 标签
- foreach 标签
- SQL 片段抽取
- 分页插件
- 分页插件介绍
- 分页插件使用
- Mybatis 多表操作
- 多表模型介绍
- 一对一
- 一对多
- 多对多
- Mybatis 注解开发
- 常用注解与案例
- MyBatis 注解开发的多表操作
- 一对一查询
- 构建 SQL
ORM(Object Relational Mapping,对象关系映射):指的是持久化数据和实体对象的映射模式,为了解决面向对象与关系型数据库存在的互不匹配的现象的技术。
- Mybatis 是一个优秀的基于 Java 的持久层框架,它内部封装了 JDBC,使开发者只需要关注 SQL 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
- 具体地说,Hibernate 是一个完全的 ORM 框架,而 Mybatis 是一个不完全的 ORM 框架。
- Mybatis 会将输入参数、输出结果进行映射。
MyBatis 官网地址
原生态 JDBC 操作:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 1、加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
// 3、定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
// 4、获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 5、设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
// 6、向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 7、遍历查询结果集
while(resultSet.next()){
User user
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//8、释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
原生态 JDBC 操作的问题与解决方案:
- 问题:
- 频繁创建和销毁数据库的连接会造成系统资源浪费从而影响系统性能。
- sql 语句在代码中硬编码,如果要修改 sql 语句,就需要修改 java 代码,造成代码不易维护。
- 查询操作时,需要手动将结果集中的数据封装到实体对象中。
- 增删改查操作需要参数时,需要手动将实体对象的数据设置到 sql 语句的占位符。
- 对应的解决方案:
- 使用数据库连接池初始化连接资源。
- 将 sql 语句抽取到配置文件中。
- 使用反射、内省等底层技术,将实体与表进行属性与字段的自动映射。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并将 Java 对象和 statement 中 SQL 的动态参数进行映射,生成最终执行的 SQL 语句。最后 Mybatis 框架执行 SQL 并将结果映射为 Java 对象并返回。
核心方法:
SqlSessionFactoryBuilder:获取 SqlSessionFactory 工厂对象的功能类
SqlSessionFactory:获取 SqlSession 构建者对象的工厂接口
SqlSession:构建者对象接口,用于执行 SQL、管理事务、接口代理
SqlSession 实例在 MyBatis 中是非常强大的一个类,在这里能看到所有执行语句、提交或回滚事务和获取映射器实例的方法。
MyBatis 开发步骤:
- 添加 MyBatis 的 jar 包
- 创建 Student 数据表
- 编写 Student 实体类
- 编写映射文件 StudentMapper.xml
- 编写核心文件 MyBatisConfig.xml
- 编写测试类
1)导入 MyBatis 的 jar 包
- mysql-connector-java-5.1.37-bin.jar
- mybatis-3.5.3.jar
- log4j-1.2.17.jar
2)创建 student 数据表
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT
);
INSERT INTO student VALUES (NULL, '张三', 23);
INSERT INTO student VALUES (NULL, '李四', 24);
INSERT INTO student VALUES (NULL, '王五', 25);
3)编写 Student 实体类
public class Student {
private Integer id;
private String name;
private Integer age;
public Student() {
}
public Student(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
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 Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
4)JDBC 配置文件
Mysql 5.X:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/world
username=root
password=itheima
Mysql 8.X:
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC
username=root
password=admin
全局配置文件包含了 MyBatis 全局的设置和属性信息,如数据库的连接、事务、连接池信息等。
全局配置文件可自定义命名,其配置内容和顺序如下(顺序不能乱):
- Properties(属性)
- Settings(全局参数设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境信息集合)
- environment(单个环境信息)
- transactionManager(事务)
- dataSource(数据源)
- mappers(映射器)
示例:src 目录下的 MyBatisConfig.xml
- jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/world
username=root
password=itheima
- MyBatisConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- MyBatis的DTD约束 -->
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 根标签 -->
<configuration>
<!-- 引入数据库连接的配置文件 -->
<properties resource="jdbc.properties"/>
<!--
<properties resource="db.properties">
<property name="db.username" value="123" />
</properties>
-->
<!-- 在日常开发过程中,排查问题时难免需要输出 MyBatis 真正执行的 SQL 语句、参数、结果等信息,这时我们就可以借助 LOG4J 的功能来实现执行信息的输出 -->
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
<!-- 起别名:即全类名与别名的映射 -->
<typeAliases>
<!-- 单个别名定义 -->
<!-- <typeAlias type="com.bean.Student" alias="student"/> -->
<!-- 批量别名定义(推荐) -->
<!-- package:为指定包下的所有类声明别名,其默认别名就是类名(首字母大小写都可) -->
<package name="com.bean" />
</typeAliases>
<!-- environments 配置数据库环境:环境可以有多个,default属性指定使用的是哪个 -->
<!-- 与spring整合后,该信息由spring来管理 -->
<environments default="mysql">
<!--environment 配置数据库环境:id属性唯一标识 -->
<environment id="mysql">
<!-- 配置JDBC事务控制,由mybatis进行管理 type属性值表示采用JDBC默认的事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- dataSource 数据源信息:type属性指表示采用mybatis连接池 -->
<dataSource type="POOLED">
<!-- property获取数据库连接的配置信息 -->
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- mappers引入映射配置文件 -->
<mappers>
<!-- mapper 引入指定的映射配置文件。resource属性指定映射配置文件的名称 -->
<mapper resource="StudentMapper.xml"/>
</mappers>
</configuration>
加载的顺序:
- 先加载 properties 中 property 标签声明的属性;
- 再加载 properties 标签引入的 java 配置文件中的属性;
- parameterType 的值和 properties 的属性值会有冲突关系(因此加上 db.)。
Mybatis 的默认别名:
Mapper:
-
<mapper resource=''/>
- 使用相对于类路径的资源
- 如:<mapper resource="sqlmap/User.xml" />
-
<mapper url=''/>
- 使用完全限定路径
- 如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
-
<mapper class=''/>
- 使用mapper接口的全限定名
- 如:<mapper class="com.mybatis.mapper.UserMapper"/>
- 注意:此种方法要求 mapper 接口和 mapper 映射文件要名称相同,且放到同一个目录下;
-
(推荐)<package name=''/>
- 注册指定包下的所有映射文件
- 如:<package name="com.mybatis.mapper"/>
- 注意:此种方法要求 mapper 接口和 mapper 映射文件要名称相同,且放到同一个目录下。
映射配置文件包含了数据和对象之间的映射关系以及要执行的 SQL 语句。
映射文件可自定义命名,一般按照规范
实体类名Mapper.xml
,这种命名规范是由 ibatis 遗留下来的。
示例:src 目录下的 StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis的DTD约束 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:核心根标签
namespace:命名空间,对statement的信息进行分类管理(在mapper代理时,它具有特殊及重要的作用)
-->
<mapper namespace="StudentMapper">
<!--
select:查询功能的标签,表示一个MappedStatement对象
id属性:statement的唯一标识
#{}:表示一个占位符(即?)
#{id}:里面的id表示输入参数的参数名称
resultType属性:输出结果的所映射的Java类型(单条结果所对应的Java类型)
这里的student之所以不用写全类名,是因为会在后面的全局配置文件中起别名
parameterType属性:输入参数的所属Java类型
-->
<!-- 查询全量学生信息 -->
<select id="selectAll" resultType="student">
SELECT * FROM student
</select>
<!-- 根据id查询指定学生信息 -->
<select id="selectById" resultType="student" parameterType="int">
SELECT * FROM student WHERE id = #{id}
</select>
<!-- 根据名称模糊查询学生列表 -->
<!-- #{} 表示占位符 ?,#{} 接收简单类型的参数时,里面的名称可以任意 -->
<!-- ${} 表示拼接符,${} 接收简单类型的参数时,里面的名称必须是 value -->
<!-- ${} 里面的值会原样输出,不加解析(如果该参数值是字符串,也不会添加引号) -->
<!-- ${} 存在sql注入的风险,但是有些场景下必须使用,比如排序后面需要动态传入排序的列名 -->
<select id="selectByName" parameterType="String" resultType="student">
SELECT * FROM student WHERE name LIKE '%${value}%'
</select>
<!-- 添加学生信息 -->
<!-- selectKey:查询主键,在标签内需要输入查询主键的sql -->
<!-- order:指定查询主键的sql和insert语句的执行顺序,相当于insert语句来说 -->
<!-- LAST_INSERT_ID:该函数是mysql的函数,获取自增主键的ID,它必须配合insert语句一起使用 -->
<insert id="insertBySelectLastId" parameterType="student">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO student (name, age) VALUES (#{name},#{age})
</insert>
<!-- 添加学生信息 -->
<!-- 只要不是主键自增,order都设置被before -->
<insert id="insertByKeyproperty" parameterType="student" useGeneratedKeys="true" keyProperty="id">
<!-- 需要显式地给id赋值,因为该id的值不是通过自增主键来创建的 -->
INSERT INTO student (id,name,age)
VALUES(#{id}, #{name}, #{age})
</insert>
<!-- 修改学生信息 -->
<update id="updateById" parameterType="student">
UPDATE student SET name = #{name}, age = #{age} WHERE id = #{id}
</update>
<!-- 删除学生信息 -->
<delete id="deleteById" parameterType="int">
DELETE FROM student WHERE id = #{id}
</delete>
</mapper>
import com.bean.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.jupiter.api.Test;
import java.io.InputStream;
import java.util.List;
public class StudentTest {
// 查询全量结果
@Test
public void selectAll() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
// sqlSession 内部的数据区域本身就是一级缓存,是通过 map 来存储的
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
List<Student> students = sqlSession.selectList("StudentMapper.selectAll"); // 映射配置文件中的namespace属性值.(SQL的)唯一标识
// 5. 处理返回结果
for (Student s : students) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 根据id查询指定结果
@Test
public void selectById() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
Student student = sqlSession.selectOne("StudentMapper.selectById", 2);
// 5. 处理返回结果
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 根据名称模糊查询结果
@Test
public void SelectByName() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
List<Student> students = sqlSession.selectList("StudentMapper.selectByName", "五");
// 5. 处理返回结果
for (Student s : students) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 新增数据:根据mysql函数自动获取id
@Test
public void insertBySelectLastId() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
Student student = new Student(null, "王八", 29); // id会自动填充
int result = sqlSession.insert("StudentMapper.insertBySelectLastId", student);
// 5. 处理返回结果
if (result==1) {
System.out.println("insertBySelectLastId 新增成功");
// 需要手动提交事务
sqlSession.commit();
} else {
System.out.println("insertBySelectLastId 新增失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 新增数据:根据映射配置属性,自动获取id
@Test
public void insertByKeyproperty() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
Student student = new Student(null, "史十", 29); // id会自动填充
int result = sqlSession.insert("StudentMapper.insertByKeyproperty", student);
// 5. 处理返回结果
if (result==1) {
System.out.println("insertByKeyproperty 新增成功");
// 需要手动提交事务
sqlSession.commit();
} else {
System.out.println("insertByKeyproperty 新增失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 修改数据
@Test
public void updateById() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
Student student = new Student(2, "小二", 29);
int result = sqlSession.update("StudentMapper.updateById", student);
// 5. 处理返回结果
if (result==1) {
System.out.println("updateById 修改成功");
// 需要手动提交事务
sqlSession.commit();
} else {
System.out.println("updateById 修改失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 删除数据
@Test
public void deleteById() {
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
// 1. 加载核心配置文件
inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过SqlSession工厂对象获取SqlSession对象
sqlSession = ssf.openSession();
// 4. 执行映射配置文件中的SQL,并获取返回结果
int result = sqlSession.delete("StudentMapper.deleteById", 2);
// 5. 处理返回结果
if (result==1) {
System.out.println("updateById 删除成功");
// 需要手动提交事务
sqlSession.commit();
} else {
System.out.println("updateById 删除失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1)简单类型
如学生 id 等基本数据类型。
2)POJO 类型
POJO(Plain Ordinary Java Object)即简单的 Java 对象,实际就是普通 Java Beans。
3)包装 POJO 类型
包装 POJO 类型,即 Java 对象中有其他 Java 对象的引用。
-
需求:
综合查询时,可能会根据用户信息、商品信息、订单信息等作为条件进行查询,用户信息中的查询条件由用户的名称和性别进行查询。
- 创建包装 POJO 类型:
- 映射文件:
- Mapper 接口:
- 测试代码:
4)Map 类型
同传递 POJO 对象一样,Map 的 key 相当于 POJO 的属性。
<!-- 传递 hashmap 综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
<!-- username是hashmap的key -->
select * from user where id=#{id} and username like '%${username}%'
</select>
Public void testFindUserByHashmap()throws Exception{
// 获取session
SqlSession session = sqlSessionFactory.openSession();
// 获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 构造查询条件Hashmap对象
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("username", "管理员");
// 传递Hashmap对象查询用户列表
List<User>list = userMapper.findUserByHashmap(map);
// 关闭session
session.close();
}
注意:当传递的 map 中的 key 和 sql 中解析的 key 不一致时,程序不会报错,只是通过 key 获取的值为空。
使用要求:
- 使用 resultType 进行结果映射时,需要查询出的列名和映射的对象的属性名一致,才能映射成功。
- 如果查询的列名和对象的属性名全部不一致,那么映射的对象为空。
- 如果查询的列名和对象的属性名有一个一致,那么映射的对象不为空,但是只有映射正确那一个属性才有值。
- 如果查询的 SQL 的列名有别名,那么这个别名就需要是和属性映射的列名。
- 使用 resultMap 进行结果映射时,不需要查询的列名和映射的属性名必须一致,但是需要声明一个 resultMap,来对列名和属性名进行映射。
示例需求:
对以下 SQL 查询的结果集进行对象映射:Select id id_, username username_, sex sex_ from user where id = 1;
<!-- resultMap入门 -->
<!-- id标签:专门为查询结果中唯一列映射 -->
<!-- result标签:映射查询结果中的普通列 -->
<resultMap type="user" id="UserRstMap">
<id column="id_" property="id" />
<result column="username_" property="username" />
<result column="sex_" property="sex" />
</resultMap>
<select id="findUserRstMap" parameterType="int" resultMap="UserRstMap">
Select id id_,username username_,sex sex_ from user where id = #{id}
</select>
原始 Dao 的开发方式,即开发 Dao 接口和 Dao 实现类。
Dao 接口:
import com.bean.Student;
import java.util.List;
public interface StudentDao {
// 1. 根据学生ID查询学生信息
public Student selectById(int id);
// 2. 根据学生名称模糊查询学生列表
public List<Student> selectByName(String name);
// 3. 添加学生
public void insertBySelectLastId(Student Student);
}
Dao 实现类:
- SqlSessionFactory:它的生命周期,应该是应用范围,即全局范围只有一个工厂,因此使用单例模式来实现这个功能(与 Spring 集成之后,由 Spring 来对其进行单例管理)。
- SqlSession:它内部含有一块数据区域,存在线程不安全的问题,所以应该将 Sqlsession 声明到方法内部。
import com.bean.Student;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
public class StudentDaoImpl implements StudentDao {
// 依赖注入
private SqlSessionFactory sqlSessionFactory;
public StudentDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public Student selectById(int id) {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 调用SqlSession的增删改查方法
// 第一个参数:表示statement的唯一标示
Student student = sqlSession.selectOne("StudentMapper.selectById", id);
System.out.println(student);
// 关闭资源
sqlSession.close();
return student;
}
@Override
public List<Student> selectByName(String name) {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 调用SqlSession的增删改查方法
// 第一个参数:表示statement的唯一标示
List<Student> list = sqlSession.selectList("StudentMapper.SelectByName", name);
System.out.println(list.toString());
// 关闭资源
sqlSession.close();
return list;
}
@Override
public void insertBySelectLastId(Student Student) {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 调用SqlSession的增删改查方法
// 第一个参数:表示statement的唯一标示
sqlSession.insert("StudentMapper.insertBySelectLastId", Student);
System.out.println(Student.getId());
// 提交事务
sqlSession.commit();
// 关闭资源
sqlSession.close();
}
}
测试类:
import com.bean.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class StudentDaoTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws IOException {
// 1. 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2. 获取SqlSession工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testSelectStudentById() {
StudentDao studentDao = new StudentDaoImpl(sqlSessionFactory);
Student student = studentDao.selectById(1);
System.out.println(student);
}
}
思考存在的问题:
- 有大量的重复的模板代码。
- 存在硬编码。
采用 Mybatis 的代理开发方式实现 Dao 层的开发,是企业的主流方式。
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。
总结:
代理方式可以让我们只编写接口即可,而实现类对象由 MyBatis 生成
。
Mapper 代理的开发规范:
- Mapper.xml(映射文件)文件中的 namespace 与 mapper 接口的全限定名相同。
- Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同。
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同。
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同。
Mapper 接口开发的方式: 程序员只需定义接口就可以对数据库进行操作。那么具体的对象是怎么创建的?
- 程序员负责定义接口;
- Mybatis 框架根据接口,通过动态代理的方式生成代理对象,负责数据库的 crud 操作。
代码示例:
import com.bean.Student;
import java.util.List;
public interface StudentMapper {
// 查询全部
public abstract List<Student> selectAll();
// 根据id查询
public abstract Student selectById(Integer id);
// 新增数据
public abstract Integer insert(Student stu);
// 修改数据
public abstract Integer update(Student stu);
// 删除数据
public abstract Integer delete(Integer id);
// 多条件查询
public abstract List<Student> selectCondition(Student stu);
// 根据多个id查询
public abstract List<Student> selectByIds(List<Integer> ids);
}
import com.bean.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.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class StudentMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws IOException {
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 获取SqlSession工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testSelectStudentById() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 由mybatis通过sqlSession来创建代理对象
// 创建StudentMapper对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectById(1);
System.out.println(student);
// 关闭资源
sqlSession.close();
inputStream.close();
}
}
分析动态代理对象如何生成的?
通过动态代理开发模式,我们只编写一个接口,不写实现类,我们通过 getMapper() 方法最终获取到 org.apache.ibatis.binding.MapperProxy 代理对象,然后执行功能,而这个代理对象正是 MyBatis 使用了 JDK 的动态代理技术,帮助我们生成了代理实现类对象。从而可以进行相关持久化操作。
分析方法是如何执行的?
动态代理实现类对象在执行方法的时候最终调用了 mapperMethod.execute() 方法,这个方法中通过 switch 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了 MyBatis 最原生的 SqlSession 方式来执行增删改查。
在 Mybatis 中提供了一些动态 SQL 标签,可以让程序员更快的进行 Mybatis 的开发,这些动态 SQL 可以提高 SQL 的可重用性。
动态 SQL 指的就是 SQL 语句可以根据条件或者参数的不同,而进行动态的变化。
常用的动态 SQL 标签有 if 标签、where 标签、SQL 片段、foreach 标签。
if 和 where 标签可用于根据实体类的不同取值,使用不同的 SQL 语句来进行查询。
比如在 id 不为空时可以根据 id 查询,在 username 不为空时还要加入用户名作为条件等。这种情况在我们的多条件组合查询中经常会碰到。
使用格式:
<!-- where:条件标签。如果有动态条件,则使用该标签代替 where 关键字 -->
<where>
<!-- if:条件判断标签 -->
<if test="条件判断">
查询条件拼接
</if>
示例:
<select id="findByCondition" parameterType="student" resultType="student">
select * from student
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
</where>
</select>
- 当查询条件 id 和 username 都存在时:
// 获得MyBatis框架生成的StudentMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student condition = new Student();
condition.setId(1);
condition.setUsername("lucy");
Student student = mapper.findByCondition(condition);
- 当查询条件只有 id 存在时:
// 获得MyBatis框架生成的UserMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper( StudentMapper.class);
Student condition = new Student();
condition.setId(1);
Student student = mapper.findByCondition(condition);
<foreach> 即循环遍历标签。适用于多个参数或者的关系。
使用语法:
<foreach collection="" open="" close="" item="" separator="">
获取参数
</foreach>
- collection:参数容器类型(list 集合、array 数组)
- open:开始的 SQL 语句
- close:结束的 SQL 语句
- item:参数变量名
- separator:分隔符
循环执行 SQL 的拼接操作,例如:SELECT * FROM student WHERE id IN (1, 2, 5);
<select id="findByIds" parameterType="list" resultType="student">
select * from student
<where>
<foreach collection="list" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
// 获得MyBatis框架生成的UserMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
List<Student> sList = mapper.findByIds(ids);
System.out.println(sList);
将重复的 SQL 提取出来,使用时用 include 引用即可,最终达到 SQL 重用的目的。
<!-- <sql>:抽取 SQL 语句标签 -->
<sql id="片段唯一标识">需要抽取的 SQL 语句</sql>
<!-- <include>:引入 SQL 片段标签 -->
<include refid="片段唯一标识id" />
使用示例:
<!-- 抽取sql片段 -->
<sql id="selectStudent" select * from student</sql>
<!-- 引入sql片段 -->
<select id="findById" parameterType="int" resultType="student">
<include refid="selectStudent"></include> where id=#{id}
</select>
<!-- 引入sql片段 -->
<select id="findByIds" parameterType="list" resultType="student">
<include refid="selectStudent"></include>
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
分页功能介绍:
- 分页可以将很多条结果进行分页显示。
- 如果当前在第一页,则没有上一页。如果当前在最后一页,则没有下一页。
- 需要明确当前是第几页,这一页中显示多少条结果。
MyBatis 分页插件:
- 在企业级开发中,分页也是一种常见的技术。而目前使用的 MyBatis 是不带分页功能的,如果想实现分页的功能,需要我们手动编写 LIMIT 语句。但是不同的数据库实现分页的 SQL 语句也是不同的,所以手写分页的成本较高,这时就可以借助分页插件来帮助我们实现分页功能。
- MyBatis 可以使用第三方的插件来对功能进行扩展,如分页插件 PageHelper 就将分页的复杂操作进行了封装,使用简单的方式即可获得分页的相关数据,从而让分页功能变得非常简单。
1)导入 jar 包
- pagehelper-5.1.10.jar
- jsqlparser-3.1.jar
2)在 Mybatis 全局配置文件中配置 PageHelper 插件
<!-- 注意:分页插件的配置位置需在通用 mapper 之前 -->
<plugins>
<!-- pageHelper 4.0 版本配置
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>-->
<!-- pageHelper 5.0 以上版本的配置 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
3)测试分页数据获取
import com.bean.Student;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
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.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class StudentMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws IOException {
// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("MyBatisConfig.xml");
// 获取SqlSession工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testPaging() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 由mybatis通过sqlSession来创建代理对象
// 创建StudentMapper对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 通过分页助手来实现分页功能
// 第一页:显示3条数据
// PageHelper.startPage(1, 3);
// 第二页:显示3条数据
// PageHelper.startPage(2, 3);
// 第三页:显示3条数据
PageHelper.startPage(3, 3);
// 5.调用实现类的方法,接收结果
List<Student> list = mapper.selectAll();
// 6. 处理结果
for (Student student : list) {
System.out.println(student);
}
// 获取分页的相关参数
PageInfo<Student> info = new PageInfo<>(list);
System.out.println("总条数:" + info.getTotal());
System.out.println("总页数:" + info.getPages());
System.out.println("每页显示条数:" + info.getPageSize());
System.out.println("当前页数:" + info.getPageNum());
System.out.println("上一页数:" + info.getPrePage());
System.out.println("下一页数:" + info.getNextPage());
System.out.println("是否是第一页:" + info.isIsFirstPage());
System.out.println("是否是最后一页:" + info.isIsLastPage());
// 关闭资源
sqlSession.close();
}
}
- 一对一:在任意一方建立外键,关联对方的主键。
- 一对多:在多的一方建立外键,关联对方(一张表)的主键。
- 多对多:借助中间表,中间表至少两个字段,分别关联两张表的主键。
案例:人和身份证,一个人只有一个身份证
1)SQL 数据准备:
CREATE TABLE person(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT
);
INSERT INTO person VALUES (NULL, '张三', 23);
INSERT INTO person VALUES (NULL, '李四', 24);
INSERT INTO person VALUES (NULL, '王五', 25);
CREATE TABLE card(
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(30),
pid INT,
CONSTRAINT cp_fk FOREIGN KEY (pid) REFERENCES person(id)
);
INSERT INTO card VALUES (NULL, '12345', 1);
INSERT INTO card VALUES (NULL, '23456', 2);
INSERT INTO card VALUES (NULL, '34567', 3);
2)实体类:
- Person 类:
package com.bean;
public class Person {
private Integer id; // 主键id
private String name; // 人的姓名
private Integer age; // 人的年龄
public Person() {
}
public Person(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
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 Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
- Card 类:
package com.bean;
public class Card {
private Integer id;
private Integer number;
private Person person; // 所属人的对象
public Card() {
}
public Card(Integer id, Integer number, Person person) {
this.id = id;
this.number = number;
this.person = person;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Card{" +
"id=" + id +
", number=" + number +
", person=" + person +
'}';
}
}
3)配置文件:
- 全局配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis的DTD约束-->
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration 核心根标签-->
<configuration>
<!--引入数据库连接的配置文件-->
<properties resource="jdbc.properties"/>
<!--配置LOG4J-->
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
<!--起别名-->
<typeAliases>
<package name="com.bean"/>
</typeAliases>
<plugins>
<!-- 注意:分页插件的配置位置需在通用 mapper 之前 -->
<!-- <plugin interceptor="com.github.pagehelper.PageHelper">-->
<!-- <!– 指定方言 –>-->
<!-- <property name="dialect" value="mysql"/>-->
<!-- </plugin>-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
<!--environments配置数据库环境,环境可以有多个。default属性指定使用的是哪个-->
<environments default="mysql">
<!--environment配置数据库环境 id属性唯一标识-->
<environment id="mysql">
<!-- transactionManager事务管理。 type属性,采用JDBC默认的事务-->
<transactionManager type="JDBC"></transactionManager>
<!-- dataSource数据源信息 type属性 连接池-->
<dataSource type="POOLED">
<!-- property获取数据库连接的配置信息 -->
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- mappers引入映射配置文件 -->
<mappers>
<!-- mapper 引入指定的映射配置文件 resource属性指定映射配置文件的名称 -->
<mapper resource="TableMapper.xml"/>
</mappers>
</configuration>
<?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.mapper.OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<!-- 使用resultMap来进行一对一结果映射,它是将关联对象添加到主信息的对象中,具体说是对象嵌套对象的一种映射方式 -->
<resultMap id="oneToOne" type="card">
<!-- id标签:建议在关联查询时必须写上,不写不会报错,但是会影响性能 -->
<id column="cid" property="id" />
<result column="number" property="number" />
<!--
association:配置被包含对象的映射关系
property:被包含对象的变量名
javaType:被包含对象的数据类型
-->
<association property="person" javaType="person">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne">
SELECT c.id cid, number, pid, NAME, age FROM card c, person p WHERE c.pid = p.id
</select>
</mapper>
4)Mapper 接口类:
import com.bean.Card;
import java.util.List;
public interface OneToOneMapper {
// 查询全部
public abstract List<Card> selectAll();
}
5)测试类:
import com.bean.Card;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class OneToOneTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class);
// 5.调用实现类的方法,接收结果
List<Card> list = mapper.selectAll();
// 6.处理结果
for (Card c : list) {
System.out.println(c);
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
案例:班级和学生,一个班级可以有多个学生
1)SQL 准备:
CREATE TABLE classes(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO classes VALUES (NULL,'一班');
INSERT INTO classes VALUES (NULL,'二班');
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30),
age INT,
cid INT,
CONSTRAINT cs_fk FOREIGN KEY (cid) REFERENCES classes(id)
);
INSERT INTO student VALUES (NULL,'张三',23,1);
INSERT INTO student VALUES (NULL,'李四',24,1);
INSERT INTO student VALUES (NULL,'王五',25,2);
INSERT INTO student VALUES (NULL,'赵六',26,2);
- 班级类:
package com.bean;
import java.util.List;
public class Classes {
private Integer id; // 主键id
private String name; // 班级名称
private List<Student> students; // 班级中所有学生对象
public Classes() {
}
public Classes(Integer id, String name, List<Student> students) {
this.id = id;
this.name = name;
this.students = students;
}
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 List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Classes{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + students +
'}';
}
}
- 学生类:
package com.bean;
import java.util.List;
public class Student {
private Integer id; // 主键id
private String name; // 学生姓名
private Integer age; // 学生年龄
private Classes classes; // 课程
public Student() {
}
public Student(Integer id, String name, Integer age, Classes classes) {
this.id = id;
this.name = name;
this.age = age;
this.classes = classes;
}
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classes=" + classes +
'}';
}
}
3)映射文件:
<?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.mapper.OneToManyMapper">
<resultMap id="oneToMany" type="classes">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
<!--
collection:配置被包含的集合对象映射关系
property:被包含对象的变量名
ofType:被包含对象的实际数据类型
-->
<collection property="students" ofType="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="oneToMany">
SELECT c.id cid, c.name cname, s.id sid, s.name sname, s.age sage FROM classes c, student s WHERE c.id=s.cid
</select>
</mapper>
4)Mapper 接口:
import com.bean.Classes;
import java.util.List;
public interface OneToManyMapper {
// 查询全部
public abstract List<Classes> selectAll();
}
package com.mapper;
import com.bean.Classes;
import com.bean.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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class OneToManyTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
OneToManyMapper mapper = sqlSession.getMapper(OneToManyMapper.class);
// 5.调用实现类的方法,接收结果
List<Classes> classes = mapper.selectAll();
// 6.处理结果
for (Classes cls : classes) {
System.out.println(cls.getId() + "," + cls.getName());
List<Student> students = cls.getStudents();
for (Student student : students) {
System.out.println("\t" + student);
}
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
案例:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择
CREATE TABLE course(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO course VALUES (NULL,'语文');
INSERT INTO course VALUES (NULL,'数学');
CREATE TABLE student_course(
id INT PRIMARY KEY AUTO_INCREMENT,
sid INT,
cid INT,
CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id),
CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id)
);
INSERT INTO student_course VALUES (NULL,1,1);
INSERT INTO student_course VALUES (NULL,1,2);
INSERT INTO student_course VALUES (NULL,2,1);
INSERT INTO student_course VALUES (NULL,2,2);
import java.util.List;
public class Student {
private Integer id; // 主键id
private String name; // 学生姓名
private Integer age; // 学生年龄
private List<Course> courses; // 学生所选择的课程集合
public Student() {
}
public Student(Integer id, String name, Integer age, List<Course> courses) {
this.id = id;
this.name = name;
this.age = age;
this.courses = courses;
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 课程类:
public class Course {
private Integer id; // 主键id
private String name; // 课程名称
public Course() {
}
public Course(Integer id, String name) {
this.id = id;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Course{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
<?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.mapper.ManyToManyMapper">
<resultMap id="manyToMany" type="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<collection property="courses" ofType="course">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="manyToMany">
SELECT sc.sid, s.name sname, s.age sage, sc.cid, c.name cname
FROM student s, course c, student_course sc
WHERE sc.sid=s.id AND sc.cid=c.id
</select>
</mapper>
import com.bean.Student;
import java.util.List;
public interface ManyToManyMapper {
// 查询全部
public abstract List<Student> selectAll();
}
package com.mapper;
import com.bean.Classes;
import com.bean.Course;
import com.bean.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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class ManyToManyTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
ManyToManyMapper mapper = sqlSession.getMapper(ManyToManyMapper.class);
//5.调用实现类的方法,接收结果
List<Student> students = mapper.selectAll();
//6.处理结果
for (Student student : students) {
System.out.println(student.getId() + "," + student.getName() + "," + student.getAge());
List<Course> courses = student.getCourses();
for (Course cours : courses) {
System.out.println("\t" + cours);
}
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
近几年来,注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。
- @Insert:实现新增
- @Update:实现更新
- @Delete:实现删除
- @Select:实现查询
- @Result:实现结果集的封装
- @Results:可以与 @Result 一起使用,以封装多个结果集
- @One:实现一对一结果集封装
- @Many:实现一对多结果集封装
案例:student 表的 CRUD
- 创建 Mapper 接口:
package com.mapper;
import com.bean.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface StudentMapper {
//查询全部
@Select("SELECT * FROM student")
public abstract List<Student> selectAll();
//新增操作
@Insert("INSERT INTO student VALUES (#{id},#{name},#{age})")
public abstract Integer insert(Student stu);
//修改操作
@Update("UPDATE student SET name=#{name},age=#{age} WHERE id=#{id}")
public abstract Integer update(Student stu);
//删除操作
@Delete("DELETE FROM student WHERE id=#{id}")
public abstract Integer delete(Integer id);
}
- 修改 Mybatis 全局配置文件:
<mappers>
<!--扫描使用注解的类-->
<mapper class="com.itheima.mapper.UserMapper">
</mappers>
或者:
<mappers>
<!--扫描使用注解的类所在的包-->
<package name="com.mapper"></package>
</mappers>
package com.mapper;
import com.bean.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.jupiter.api.Test;
import java.io.InputStream;
import java.util.List;
public class AnnotationTest {
@Test
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Student> list = mapper.selectAll();
//6.处理结果
for (Student student : list) {
System.out.println(student);
}
//7.释放资源
sqlSession.close();
is.close();
}
@Test
public void insert() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
Student stu = new Student(4, "赵六", 26);
Integer result = mapper.insert(stu);
//6.处理结果
System.out.println(result);
//7.释放资源
sqlSession.close();
is.close();
}
@Test
public void update() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
Student stu = new Student(4, "赵六", 36);
Integer result = mapper.update(stu);
//6.处理结果
System.out.println(result);
//7.释放资源
sqlSession.close();
is.close();
}
@Test
public void delete() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
Integer result = mapper.delete(4);
//6.处理结果
System.out.println(result);
//7.释放资源
sqlSession.close();
is.close();
}
}
实现复杂关系映射之前我们可以在映射文件中通过配置 <resultMap> 来实现,使用注解开发后,我们可以使用 @Results、@Result、@One、@Many 注解组合来完成复杂关系的配置。
需求:查询一个用户信息,与此同时查询出该用户对应的身份证信息
1)对应 SQL:
SELECT * FROM card;
SELECT * FROM person WHERE id=#{id};
2)创建 PersonMapper 接口:
import com.bean.Person;
import org.apache.ibatis.annotations.Select;
public interface PersonMapper {
// 根据id查询
@Select("SELECT * FROM person WHERE id=#{id}")
public abstract Person selectById(Integer id);
}
3)使用注解配置 CardMapper:
import com.bean.Card;
import com.bean.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface CardMapper {
// 查询全部
@Select("SELECT * FROM card")
@Results({
@Result(column="id", property="id"), // id 列
@Result(column="number", property="number"), // number 列
@Result( // Card表中的 person id 列
property = "person", // 被包含对象的变量名
javaType = Person.class, // 被包含对象的实际数据类型
column = "pid", // 根据查询出的card表中的pid字段来查询person表
/*
one、@One 一对一固定写法
select属性:指定调用哪个接口中的哪个方法
*/
one = @One(select="com.mapper.PersonMapper.selectById")
)
})
public abstract List<Card> selectAll();
}
4)测试类:
import com.bean.Card;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class OneToOneTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
CardMapper mapper = sqlSession.getMapper(CardMapper.class);
// 5.调用实现类的方法,接收结果
List<Card> list = mapper.selectAll();
// 6.处理结果
for (Card c : list) {
System.out.println(c);
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
执行结果:
Card{id=1, number=12345, person=Person{id=1, name='张三', age=23}}
Card{id=2, number=23456, person=Person{id=2, name='李四', age=24}}
Card{id=3, number=34567, person=Person{id=3, name='王五', age=25}}
需求:查询一个课程,与此同时查询出该课程对应的学生信息
1)对应的 SQL:
SELECT * FROM classes
SELECT * FROM student WHERE cid=#{cid}
2)创建 StudentMapper 接口:
import com.bean.Student;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface StudentMapper {
//根据cid查询student表
@Select("SELECT * FROM student WHERE cid=#{cid}")
public abstract List<Student> selectByCid(Integer cid);
}
package com.mapper;
import com.bean.Card;
import com.bean.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface CardMapper {
// 查询全部
@Select("SELECT * FROM card")
@Results({
@Result(column="id", property="id"), // id 列
@Result(column="number", property="number"), // number 列
@Result( // Card表中的 person id 列
property = "person", // 被包含对象的变量名
javaType = Person.class, // 被包含对象的实际数据类型
column = "pid", // 根据查询出的card表中的pid字段来查询person表
/*
one、@One 一对一固定写法
select属性:指定调用哪个接口中的哪个方法
*/
one = @One(select="com.mapper.PersonMapper.selectById")
)
})
public abstract List<Card> selectAll();
}
package com.mapper;
import com.bean.Classes;
import com.bean.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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class OneToManyTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class);
// 5.调用实现类的方法,接收结果
List<Classes> classes = mapper.selectAll();
// 6.处理结果
for (Classes cls : classes) {
System.out.println(cls.getId() + "," + cls.getName());
List<Student> students = cls.getStudents();
for (Student student : students) {
System.out.println("\t" + student);
}
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
1,一班
Student{id=1, name='张三', age=23}
Student{id=2, name='李四', age=24}
2,二班
Student{id=3, name='王五', age=25}
需求:查询学生以及所对应的课程信息
SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id
SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id}
2)创建 CourseMapper 接口:
import com.bean.Course;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface CourseMapper {
// 根据学生id查询所选课程
@Select("SELECT c.id, c.name FROM stu_cr sc, course c WHERE sc.cid=c.id AND sc.sid=#{id}")
public abstract List<Course> selectBySid(Integer id);
}
3)使用注解配置 StudentMapper 接口:
package com.mapper;
import com.bean.Student;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface StudentMapper {
// 查询全部
@Select("SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id")
@Results({
@Result(column="id", property="id"),
@Result(column="name", property="name"),
@Result(column="age", property="age"),
@Result(
property="courses", // 被包含对象的变量名
javaType=List.class, // 被包含对象的实际数据类型
column="id", // 根据查询出student表的id来作为关联条件,去查询中间表和课程表
/*
many、@Many 一对多查询的固定写法
select属性:指定调用哪个接口中的哪个查询方法
*/
many = @Many(select="com.mapper.CourseMapper.selectBySid")
)
})
public abstract List<Student> selectAll();
}
package com.mapper;
import com.bean.Classes;
import com.bean.Course;
import com.bean.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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class ManyToManyTest {
@Test
public void testSelectAll() throws IOException {
// 1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 4.获取OneToOneMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.调用实现类对象中的方法,接收结果
List<Student> list = mapper.selectAll();
// 6.处理结果
for (Student student : list) {
System.out.println(student.getId() + "," + student.getName() + "," + student.getAge());
List<Course> courses = student.getCourses();
for (Course cours : courses) {
System.out.println("\t" + cours);
}
}
// 7.释放资源
sqlSession.close();
is.close();
}
}
1,张三,23
Course{id=1, name='语文'}
Course{id=2, name='数学'}
2,李四,24
Course{id=1, name='语文'}
Course{id=2, name='数学'}
之前在通过注解开发时,相关 SQL 语句都是自己直接拼写的,一些关键字写起来比较麻烦、而且容易出错。因此,MyBatis 给我们提供了 org.apache.ibatis.jdbc.SQL 功能类,专门用于构建 SQL 语句。
查询功能的实现:
- 定义功能类并提供获取查询的 SQL 语句的方法。
- @SelectProvider:生成查询用的 SQL 语句注解。
- type 属性:生成 SQL 语句功能类对象
- method 属性:指定调用方法
新增功能的实现:
- 定义功能类并提供获取新增的 SQL 语句的方法。
- @InsertProvider:生成新增用的 SQL 语句注解。
修改功能的实现:
- 定义功能类并提供获取修改的 SQL 语句的方法。
- @UpdateProvider:生成修改用的 SQL 语句注解。
删除功能的实现:
- 定义功能类并提供获取删除的 SQL 语句的方法。
- @DeleteProvider:生成删除用的 SQL 语句注解。
案例:
- Dao 层:
import com.itheima.domain.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.ArrayList;
/*
Dao层接口
*/
public interface StudentDao {
//查询所有学生信息
@Select("SELECT * FROM student")
public abstract ArrayList<Student> findAll();
//条件查询,根据id获取学生信息
@Select("SELECT * FROM student WHERE sid=#{sid}")
public abstract Student findById(Integer sid);
//新增学生信息
@Insert("INSERT INTO student VALUES (#{sid},#{name},#{age},#{birthday})")
public abstract int insert(Student stu);
//修改学生信息
@Update("UPDATE student SET name=#{name},age=#{age},birthday=#{birthday} WHERE sid=#{sid}")
public abstract int update(Student stu);
//删除学生信息
@Delete("DELETE FROM student WHERE sid=#{sid}")
public abstract int delete(Integer sid);
}
import com.itheima.dao.StudentDao;
import com.itheima.domain.Student;
import com.itheima.service.StudentService;
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.ArrayList;
import java.util.List;
/**
* 业务层实现类
*/
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> findAll() {
ArrayList<Student> list = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentDao接口的实现类对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//5.调用实现类对象的方法,接收结果
list = mapper.findAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return list;
}
@Override
public Student findById(Integer sid) {
Student stu = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentDao接口的实现类对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//5.调用实现类对象的方法,接收结果
stu = mapper.findById(sid);
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return stu;
}
@Override
public void save(Student student) {
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentDao接口的实现类对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//5.调用实现类对象的方法,接收结果
mapper.insert(student);
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void update(Student student) {
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentDao接口的实现类对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//5.调用实现类对象的方法,接收结果
mapper.update(student);
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void delete(Integer sid) {
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentDao接口的实现类对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//5.调用实现类对象的方法,接收结果
mapper.delete(sid);
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}