Mybatis
Java三层架构
三层架构就是把整个软件系统分为三个层次
- 表现层(Presentation layer)
- 业务逻辑层(Business Logic Layer)
- 数据访问层(Data access layer)
表现层俗称web层,使用各种前端的技术展示页面。
业务层俗称service层,从数据库中查询到的数据需要在这里进行处理。
数据层俗称Dao(Data Access Object)层或者持久层,持久的意思是把数据从可以持久保存的磁盘读取到不能持久保存的内存中。
ORM
-
ORM(Object Relational Mapping): 对象关系映射
-
指的是持久化数据和实体对象的映射模式,为了解决面向对象与关系型数据库存在的互不匹配的现象的技术。
-
如图:
-
具体映射关系如下图:
原始jdbc操作的缺点
存在的问题:
- 频繁创建和销毁数据库的连接会造成系统资源浪费从而影响系统性能。使用数据库连接池就能在一定程度上缓解该问题
- sql 语句在Java代码中编写,如果要修改 sql 语句,就需要修改 java 代码,造成代码不易维护。
- 使用
preparedStatement
向占位符传递参数时存在硬编码,也会进一步加大系统维护的难度 - 对结果集进行解析时存在硬编码,SQL 变化将导致解析代码改变,系统难以维护,但如果能将数据库记录进行封装成 POJO 对象,解析起来就会方便很多。
- 查询操作时,需要手动将结果集中的数据封装到实体对象中。
问题解决方案
- 使用数据库连接池初始化连接资源。
- 将 sql 语句抽取到配置文件中。
- 使用反射、内省等底层技术,将实体与表进行属性与字段的自动映射
Mybatis概念
MyBatis 是一个持久层框架,是一个半自动化的 ORM 框架,不仅支持自定义 SQL、存储过程以及高级映射。还简化了 JDBC 代码以及设置参数和获取结果集的工作过程,使得我们只需要通过简单的 XML 或注解配置,就能将原始类型、接口和 Java POJO 映射为数据库中的记录。通过对 JDBC 操作数据库的过程进行封装,我们只需要关注 SQL 而不用再去处理注册驱动、创建 connection
、创建 statement
、手动设置参数、结果集检索等复杂的过程代码。
总的来讲,MyBatis 有如下优点:
- 简单易学:自身小且简单,无任何第三方依赖;
- 灵活:MyBatis 不会对应用程序或数据库的现有设计强加任何影响,写在 XML 中,便于统一管理和优化;
- 解除 SQL 与代码程序的耦合:通过提供 DAO 层,将业务逻辑与数据访问逻辑分离,使系统设计更加清晰、易维护、易于单元测试,提高了程序的可维护性;
- 提供 XML 标签,支持编写动态 SQL;
MyBatis官网地址:http://www.mybatis.org/mybatis-3/
Mybatis的快速入门
步骤:
- 添加 MyBatis 的jar包
包括 mysql 的驱动包和 log4j 的包 - 创建 Student 数据表
- 编写 Studentr 实体类和 StudentMapper 接口
- 编写映射文件 StudentMapper.xml
映射配置文件中的 namespace 属性与 Mapper 接口一样,这样可以使用接口代理方式开发
先在核心文件中起好别名,就不用在 resultType 和 parameterType 中写包名了 - 编写核心文件MyBatisConfig.xml
先准备好jdbc.properties和log4j.properties,导入核心文件中。别忘了起别名 - 编写测试类
环境搭建
1.导入MyBatis的jar包
- mysql-connector-java-5.1.37-bin.jar
- mybatis-3.5.3.jar
- log4j-1.2.17.jar
2.创建student数据表
3.编写Student实体
public class Student {
private Integer id;
private String name;
private Integer age;
//省略getter、setter
}
4.编写StudentMapper.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属性:名称空间
-->
<mapper namespace="StudentMapper">
<!--
select:查询功能的标签
id属性:唯一标识 与namespace结合起来选择要执行的SQL语句
resultType属性:指定结果映射对象类型
parameterType属性:指定参数映射对象类型 bean可以自动填入
SQL 获取参数: #{属性名}
-->
<select id="selectAll" resultType="com.example.bean.Student">
select * from student
</select>
<select id="selectById" resultType="com.example.bean.Student" parameterType="java.lang.Integer">
select * from student where id = #{id}
</select>
<insert id="insert" parameterType="com.example.bean.Student">
insert into student values (#{id},#{name},#{age})
</insert>
<update id = "update" parameterType="com.example.bean.Student" >
update student SET name = #{name},age = #{age},where id = #{id}
</update>
<delete id="delete" parameterType="java.lang.Integer">
delete from student where id = #{id}
</delete>
</mapper>
5.编写MyBatis核心文件
<?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>
<!--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="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db4" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- mappers引入映射配置文件 -->
<mappers>
<!-- mapper 引入指定的映射配置文件 resource属性指定映射配置文件的名称 -->
<mapper resource="StudentMapper.xml"/>
</mappers>
</configuration>
测试代码
package com.itheima.dao;
import com.itheima.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.Test;
import java.io.InputStream;
import java.util.List;
public class StudentTest01 {
/*
查询全部
*/
@Test
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//InputStream is = StudentTest01.class.getClassLoader().getResourceAsStream("MyBatisConfig.xml"); 这是以前加载配置文件的方法
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过SqlSession工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.执行映射配置文件中的sql语句,并接收结果
List<Student> list = sqlSession.selectList("StudentMapper.selectAll");
//5.处理结果
for (Student stu : list) {
System.out.println(stu);
}
//6.释放资源
sqlSession.close();
is.close();
}
}
MyBatis的相关api
Resources
-
org.apache.ibatis.io.Resources:加载资源的工具类。
-
核心方法
构建器SqlSessionFactoryBuilder
-
org.apache.ibatis.session.SqlSessionFactoryBuilder:获取 SqlSessionFactory 工厂对象的功能类
-
核心方法
-
通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。
工厂对象SqlSessionFactory
-
org.apache.ibatis.session.SqlSessionFactory:获取 SqlSession 构建者对象的工厂接口。
-
核心api
SqlSession会话对象
-
org.apache.ibatis.session.SqlSession:构建者对象接口。用于执行 SQL、管理事务、接口代理。
-
核心api
SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。
映射配置文件
要执行的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属性:名称空间
-->
<mapper namespace="StudentMapper">
<!--
select:查询功能的标签 insert update delete 对应SQL语句的类型
id属性:唯一标识 与namespace结合起来选择要执行的SQL语句
resultType属性:指定结果集映射的对象类型
parameterType属性:指定参数映射对象类型 bean可以自动填入
SQL 获取参数: #{属性名}
-->
<select id="selectAll" resultType="com.example.bean.Student">
select * from student
</select>
<select id="selectById" resultType="com.example.bean.Student" parameterType="java.lang.Integer">
select * from student where id = #{id}
</select>
<insert id="insert" parameterType="com.example.bean.Student">
insert into student values (#{id},#{name},#{age})
</insert>
<update id = "update" parameterType="com.example.bean.Student" >
update student SET name = #{name},age = #{age},where id = #{id}
</update>
<delete id="delete" parameterType="java.lang.Integer">
delete from student where id = #{id}
</delete>
</mapper>
测试代码:
package com.example.Test;
import com.example.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.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @author shenguopin
* @date 2021/8/17 11:32
*/
public class StudentTest01 {
@Test
public void selectAll() throws IOException {
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过SqlSession工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.执行映射配置文件中的sql语句,并接收结果
List<Student> list = sqlSession.selectList("StudentMapper.selectAll");
//5.处理结果
for (Student student : list) {
System.out.println(student);
}
//6.释放资源
is.close();
sqlSession.close();
}
@Test
public void insert() throws IOException {
//加载核心配置文件,获取工厂对象,获取SQLsession
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行insert
Student stu = new Student(4,"张三丰",302);
int result = sqlSession.insert("StudentMapper.insert", stu);
//必须手动提交事务
sqlSession.commit();
//查看一下提交的结果
System.out.println(result);
//释放资源
is.close();
sqlSession.close();
}
@Test
public void delete() throws IOException {
//加载核心配置文件,获取工厂对象,获取SQLsession
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行delete
int result = sqlSession.delete("StudentMapper.delete", 4);
//必须手动提交事务
sqlSession.commit();
//查看一下提交的结果
System.out.println(result);
//释放资源
is.close();
sqlSession.close();
}
}
核心配置文件
核心配置文件包含了 MyBatis 最核心的设置和属性信息。如数据库的连接、事务、连接池信息等。
引入连接配置文件
之前使用的核心配置文件将数据库的url、用户登录等信息直接写在了文件中,也可以抽取到单独的配置文件中。
新建一个 jdbc.properties, 将 url 等信息都写入。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db4
username=root
password=root
然后在核心配置文件中将上面的连接配置文件引入
-
properties标签引入外部文件
<!--引入数据库连接的配置文件--> <properties resource="jdbc.properties"/>
-
具体使用,如下配置
<!-- property获取数据库连接的配置信息 --> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" />
起别名
在映射配置文件里的 resultType="com.example.bean.Student"
写起来太麻烦了,可以在核心配置文件中给类起别名,就可以简化编写了。
-
<typeAliases>:为全类名起别名的父标签。
-
<typeAlias>:为全类名起别名的子标签。
-
属性
type:指定全类名
alias:指定别名
-
<package>:为指定包下所有类起别名的子标签。(别名就是类名)
-
如下图:
-
具体如下配置
<!--起别名--> <typeAliases> <typeAlias type="com.itheima.bean.Student" alias="student"/> <!--<package name="com.itheima.bean"/>--> </typeAliase>
-
注意:起别名的标签在核心配置文件里,但是使用是在映射配置文件里。起好别名之后映射配置文件就不用写全类名了
核心配置文件完整实现
<?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>
<typeAlias type="com.example.bean.Student" alias="student"/>
</typeAliases>
<!--environments配置数据库环境,环境可以有多个。default属性指定使用的是哪个-->
<environments default="mysql">
<!--environment配置数据库环境 id属性唯一标识-->
<environment id="mysql">
<!-- transactionManager事务管理。 type属性,采用JDBC默认的事务-->
<transactionManager type="JDBC" />
<!-- 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="StudentMapper.xml"/>
</mappers>
</configuration>
Log4J
前面的sql语句都写到了映射配置文件中,在控制台输出查询结果的时候我们并不知道执行的究竟是什么SQL语句,Log4J可以将SQL语句和查询结果一起输出到控制台,是用来调试 MyBatis 的工具。
使用步骤:
-
导入jar包
-
修改核心配置文件
<!--配置LOG4J--> <settings> <setting name="logImpl" value="log4j"/> </settings>
-
编写log4j.properties
# Global logging configuration log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
注意:需要把properties文件放到 /scr 下,如果还是报错,看一下out目录下其他配置文件的位置,把 properties 复制过去。
接口代理方式实现Dao
代理开发方式介绍
传统的开发需要编写 DAO 层,service 层,controller 层,而且需要接口和实现类。而 MyBatista 的代理开发方式可以方便的实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,不需要我们在编写实现类了。
Mapper 接口开发需要遵循以下规范:
1) 映射配置文件中的 namespace 与 mapper 接口的全限定名相同
2) Mapper 接口方法名和映射配置文件中定义的每个 sql 语句的 id 相同
3) Mapper 接口方法的输入参数类型和映射配置文件中定义的每个 sql 的 parameterType 的类型相同
4) Mapper 接口方法的返回值类型和映射配置文件中定义的每个 sql 的 resultType 的类型相同
总结:
接口开发的方式: 程序员只需定义接口,就可以对数据库进行操作,那么具体的对象怎么创建?
1.程序员负责定义接口
2.在操作数据库,mybatis框架根据接口,通过动态代理的方式生成代理对象,负责数据库的crud操作
编写StudentMapper接口
测试代理方式
public Student selectById(Integer id) {
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.获取StudentMapper接口的实现类对象
//以前是通过service的实现类获取dao的实现类,现在不需要dao的实现类了,mybatis自动生成
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
stu = mapper.selectById(id);
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return stu;
}
源码分析
-
分析动态代理对象如何生成的?
通过动态代理开发模式,我们只编写一个接口,不写实现类,我们通过 getMapper() 方法最终获取到 org.apache.ibatis.binding.MapperProxy 代理对象,然后执行功能,而这个代理对象正是 MyBatis 使用了 JDK 的动态代理技术,帮助我们生成了代理实现类对象。从而可以进行相关持久化操作。
-
分析方法是如何执行的?
动态代理实现类对象在执行方法的时候最终调用了 mapperMethod.execute() 方法,这个方法中通过 switch 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了 MyBatis 最原生的 SqlSession 方式来执行增删改查。
知识小结
接口代理方式可以让我们只编写接口即可,而实现类对象由 MyBatis 生成。
实现规则 :
- 映射配置文件中的名称空间必须和 Dao 层接口的全类名相同。
- 映射配置文件中的增删改查标签的 id 属性必须和 Dao 层接口的方法名相同。
- 映射配置文件中的增删改查标签的 parameterType 属性必须和 Dao 层接口方法的参数相同。
- 映射配置文件中的增删改查标签的 resultType 属性必须和 Dao 层接口方法的返回值相同。
- 获取动态代理对象 SqlSession 功能类中的 getMapper() 方法。
动态sql
在前面的映射文件中,我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。比如根据传入的多个id自动获取多条记录,这就需要用到动态sql了。
<if>
我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。只有id为参数时,根据id获取记录;既有 id 又有 name 就同时根据这两个条件查询记录。这种情况在我们的多条件组合查询中经常会碰到。
<select id="selectCondition" parameterType="student" resultType="student">
select * from student
<where>
<if test="id != null">
id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
总结:
<where>:条件标签。如果有动态条件,则使用该标签代替 where 关键字。
<if>:条件判断标签。
<if test=“条件判断”>
查询条件拼接
</if>
<foreach>
循环执行sql的拼接操作,例如:SELECT * FROM student WHERE id IN (1,2,5)。
<select id="selectByIds" parameterType="list" resultType="student">
select * from student
<where>
<foreach collection="list" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
测试代码片段如下:
… … …
//获得MyBatis框架生成的StudentMapper接口的实现类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int[] ids = new int[]{2,5};
List<Student> list = mapper.selectByIds(ids);
System.out.println(list);
… … …
总结语法:
<foreach>:循环遍历标签。适用于多个参数或者的关系。
<foreach collection=“”open=“”close=“”item=“”separator=“”>
获取参数
</foreach>
属性
collection:参数容器类型, (list-集合, array-数组)。
open:开始的 SQL 语句。
close:结束的 SQL 语句。
item:参数变量名。
separator:分隔符。
SQL片段抽取
映射配置文件中可将重复的 sql 提取出来放到标签中,使用时通过 <include> 标签引入,以达到复用的效果。
<!--抽取sql片段简化编写-->
<sql id="select" >select * from student</sql>
<!-- 使用sql片段-->
<select id="selectById" resultType="student" parameterType="java.lang.Integer">
<include refid="select"/> where id = #{id}
</select>
总结语法:
- <sql>:抽取 SQL 语句标签。
- <include>:引入 SQL 片段标签。
<sql id=“片段唯一标识”>抽取的 SQL 语句</sql> <include refid=“片段唯一标识”/>
知识小结
MyBatis映射文件配置:
<select>:查询
<insert>:插入
<update>:修改
<delete>:删除
<where>:where条件
<if>:if判断
<foreach>:循环
<sql>:sql片段抽取
分页插件
在企业级开发中,分页也是一种常见的技术。而目前使用的 MyBatis 是不带分页功能的,如果想实现分页的功能,需要我们手动编写 LIMIT 语句。但是不同的数据库实现分页的 SQL 语句也是不同的,所以手写分页成本较高。这个时候就可以借助分页插件来帮助我们实现分页功能。
PageHelper:第三方分页助手。将复杂的分页操作进行封装,从而让分页功能变得非常简单。
使用
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
使用步骤:
①导入与PageHelper的jar包
②在mybatis核心配置文件中配置PageHelper插件
<!-- 注意:分页助手的插件 配置在通用mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
③测试分页数据获取
@Test
public void testPageHelper(){
//设置分页参数
PageHelper.startPage(1,2);
List<User> select = userMapper2.select(null);
for(User user : select){
System.out.println(user);
}
}
分页插件的参数获取
获得分页相关的其他参数:
//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
知识小结
分页:可以将很多条结果进行分页显示。
-
分页插件 jar 包: pagehelper-5.1.10.jar jsqlparser-3.1.jar
-
<plugins>:集成插件标签。
-
分页助手相关 API
1.PageHelper:分页助手功能类。
2. startPage():设置分页参数
3. PageInfo:分页相关参数功能类。
4. getTotal():获取总条数
5. getPages():获取总页数
6. getPageNum():获取当前页
7. getPageSize():获取每页显示条数
8. getPrePage():获取上一页
9. getNextPage():获取下一页
10. isIsFirstPage():获取是否是第一页
11. isIsLastPage():获取是否是最后一页
多表操作
多表模型介绍
我们之前学习的都是基于单表操作的,而实际开发中,随着业务难度的加深,肯定需要多表操作的。
- 多表模型分类 一对一:在任意一方建立外键,关联对方的主键。
- 一对多:在多的一方建立外键,关联一的一方的主键。
- 多对多:借助中间表,中间表至少两个字段,分别关联两张表的主键。
一对一操作
-
一对一模型: 人和身份证,一个人只有一个身份证
-
代码实现
-
步骤一: 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);
-
步骤二:配置映射文件
<?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.itheima.table01.OneToOneMapper"> <!--配置字段和实体对象属性的映射关系--> <resultMap id="oneToOne" type="card"> <id column="cid" property="id" /> <result column="number" property="number" /> <!-- association:配置被包含对象的映射关系 property:被包含对象的变量名 javaType:被包含对象的数据类型 --> <association property="p" 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>
-
步骤三:测试类
@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.获取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(); }
3.一对一配置总结:
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型 <id>:配置主键映射关系标签。 <result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称 <association>:配置被包含对象的映射关系标签。 property 属性:被包含对象的变量名 javaType 属性:被包含对象的数据类型
-
一对多操作
-
一对多模型: 一对多模型:班级和学生,一个班级可以有多个学生。
-
代码实现
-
步骤一: 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);
-
步骤二:配置文件
<mapper namespace="com.itheima.table02.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>
-
步骤三:测试类
@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.获取OneToManyMapper接口的实现类对象 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(); }
3.一对多配置文件总结:
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型 <id>:配置主键映射关系标签。 <result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称 <collection>:配置被包含集合对象的映射关系标签。 property 属性:被包含集合对象的变量名 ofType 属性:集合中保存的对象数据类型 ```xml
-
多对多操作
-
多对多模型:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择。
-
代码实现
-
步骤一: sql语句准备
CREATE TABLE course( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20) ); INSERT INTO course VALUES (NULL,'语文'); INSERT INTO course VALUES (NULL,'数学'); CREATE TABLE stu_cr( 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 stu_cr VALUES (NULL,1,1); INSERT INTO stu_cr VALUES (NULL,1,2); INSERT INTO stu_cr VALUES (NULL,2,1); INSERT INTO stu_cr VALUES (NULL,2,2);
-
步骤二:配置文件
<?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.itheima.table03.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,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id </select> </mapper>
-
步骤三:测试类
@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.获取ManyToManyMapper接口的实现类对象 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(); }
3.多对多配置文件总结:
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型 <id>:配置主键映射关系标签。 <result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称 <collection>:配置被包含集合对象的映射关系标签。 property 属性:被包含集合对象的变量名 ofType 属性:集合中保存的对象数据类型
-
多表模型操作总结
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<association>:配置被包含对象的映射关系标签。
property 属性:被包含对象的变量名
javaType 属性:被包含对象的数据类型
<collection>:配置被包含集合对象的映射关系标签。
property 属性:被包含集合对象的变量名
ofType 属性:集合中保存的对象数据类型
注解开发单表操作
MyBatis的常用注解
这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
增删改查
我们完成简单的student表的增删改查的操作
-
步骤一:创建mapper接口
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); }
-
步骤二:测试类
public class Test01 { @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(); } }
-
注意:
修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可
<mappers>
<!--扫描使用注解的类-->
<mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>
或者指定扫描包含映射关系的接口所在的包也可以
<mappers>
<!--扫描使用注解的类所在的包-->
<package name="com.itheima.mapper"></package>
</mappers>
注解开发总结
注解可以简化开发操作,省略映射配置文件的编写。
-
常用注解
@Select(“查询的 SQL 语句”):执行查询操作注解
@Insert(“查询的 SQL 语句”):执行新增操作注解
@Update(“查询的 SQL 语句”):执行修改操作注解
@Delete(“查询的 SQL 语句”):执行删除操作注解
-
配置映射关系
<mappers> <package name="接口所在包"/> </mappers>
注解开发多表操作
MyBatis的注解实现复杂映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
一对一查询
一对一查询的模型
一对一查询的需求:查询一个用户信息,与此同时查询出该用户对应的身份证信息
一对一查询的语句
对应的sql语句:
SELECT * FROM card;
SELECT * FROM person WHERE id=#{id};
创建PersonMapper接口
public interface PersonMapper {
//根据id查询
@Select("SELECT * FROM person WHERE id=#{id}")
public abstract Person selectById(Integer id);
}
使用注解配置Mapper
public interface CardMapper {
//查询全部
@Select("SELECT * FROM card")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "number",property = "number"),
@Result(
property = "p", // 被包含对象的变量名
javaType = Person.class, // 被包含对象的实际数据类型
column = "pid", // 根据查询出的card表中的pid字段来查询person表
/*
one、@One 一对一固定写法
select属性:指定调用哪个接口中的哪个方法
*/
one = @One(select = "com.itheima.one_to_one.PersonMapper.selectById")
)
})
public abstract List<Card> selectAll();
}
测试类
public class Test01 {
@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.获取CardMapper接口的实现类对象
CardMapper mapper = sqlSession.getMapper(CardMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Card> list = mapper.selectAll();
//6.处理结果
for (Card card : list) {
System.out.println(card);
}
//7.释放资源
sqlSession.close();
is.close();
}
}
一对一配置总结
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
one 属性:一对一查询固定属性
@One:一对一查询的注解。
select 属性:指定调用某个接口中的方法
一对多查询
一对多查询的模型
一对多查询的需求:查询一个课程,与此同时查询出该该课程对应的学生信息
一对多查询的语句
对应的sql语句:
SELECT * FROM classes
SELECT * FROM student WHERE cid=#{cid}
创建StudentMapper接口
public interface StudentMapper {
//根据cid查询student表
@Select("SELECT * FROM student WHERE cid=#{cid}")
public abstract List<Student> selectByCid(Integer cid);
}
使用注解配置Mapper
public interface ClassesMapper {
//查询全部
@Select("SELECT * FROM classes")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(
property = "students", // 被包含对象的变量名
javaType = List.class, // 被包含对象的实际数据类型
column = "id", // 根据查询出的classes表的id字段来查询student表
/*
many、@Many 一对多查询的固定写法
select属性:指定调用哪个接口中的哪个查询方法
*/
many = @Many(select = "com.itheima.one_to_many.StudentMapper.selectByCid")
)
})
public abstract List<Classes> selectAll();
}
测试类
public class Test01 {
@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.获取ClassesMapper接口的实现类对象
ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Classes> list = mapper.selectAll();
//6.处理结果
for (Classes cls : list) {
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();
}
}
一对多配置总结
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
many 属性:一对多查询固定属性
@Many:一对多查询的注解。
select 属性:指定调用某个接口中的方法
多对多查询
多对多查询的模型
多对多查询的需求:查询学生以及所对应的课程信息
多对多查询的语句
对应的sql语句:
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}
添加CourseMapper 接口方法
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);
}
使用注解配置Mapper
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.itheima.many_to_many.CourseMapper.selectBySid")
)
})
public abstract List<Student> selectAll();
}
测试类
public class Test01 {
@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.getId() + "," + student.getName() + "," + student.getAge());
List<Course> courses = student.getCourses();
for (Course cours : courses) {
System.out.println("\t" + cours);
}
}
//7.释放资源
sqlSession.close();
is.close();
}
}
多对多配置总结
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
many 属性:一对多查询固定属性
@Many:一对多查询的注解。
select 属性:指定调用某个接口中的方法
构建sql
- 我们之前通过注解开发时,相关 SQL 语句都是自己直接拼写的。一些关键字写起来比较麻烦、而且容易出错。
- MyBatis 给我们提供了 org.apache.ibatis.jdbc.SQL 功能类,专门用于构建 SQL 语句。不过只能用于简单的单表查询,用处不大。
语法
-
定义功能类并提供获取查询的 SQL 语句的方法。
-
@SelectProvider:生成查询用的 SQL 语句注解。
-
@InsertProvider:生成新增用的 SQL 语句注解。
-
@UpdateProvider:生成修改用的 SQL 语句注解。
-
@DeleteProvider:生成删除用的 SQL 语句注解。
type 属性:生成 SQL 语句功能类对象
method 属性:指定调用方法
示例代码
用来生成 sql 语句的类
public class ReturnSql {
//定义方法,返回查询的sql语句
public String getSelectAll() {
return new SQL() {
{
SELECT("*");
FROM("student");
}
}.toString();
}
//定义方法,返回新增的sql语句
public String getInsert(Student stu) {
return new SQL() {
{
INSERT_INTO("student");
INTO_VALUES("#{id},#{name},#{age}");
}
}.toString();
}
//定义方法,返回修改的sql语句
public String getUpdate(Student stu) {
return new SQL() {
{
UPDATE("student");
SET("name=#{name}","age=#{age}");
WHERE("id=#{id}");
}
}.toString();
}
//定义方法,返回删除的sql语句
public String getDelete(Integer id) {
return new SQL() {
{
DELETE_FROM("student");
WHERE("id=#{id}");
}
}.toString();
}
}
Mapper接口,通过注解引入通过 SQL 功能类生成的 SQL 语句
public interface StudentMapper {
//查询全部
//@Select("SELECT * FROM student")
@SelectProvider(type = ReturnSql.class , method = "getSelectAll")
public abstract List<Student> selectAll();
//新增功能
//@Insert("INSERT INTO student VALUES (#{id},#{name},#{age})")
@InsertProvider(type = ReturnSql.class , method = "getInsert")
public abstract Integer insert(Student stu);
//修改功能
//@Update("UPDATE student SET name=#{name},age=#{age} WHERE id=#{id}")
@UpdateProvider(type = ReturnSql.class , method = "getUpdate")
public abstract Integer update(Student stu);
//删除功能
//@Delete("DELETE FROM student WHERE id=#{id}")
@DeleteProvider(type = ReturnSql.class , method = "getDelete")
public abstract Integer delete(Integer id);
}