Mybatis
1. Mybatis 介绍
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录. 半自动化框架。必须写sql语句。 ---->
JDBC->dbutils->MyBatis->Hibernate
2. mybatis 快速入门
编写第一个基于 mybaits 的测试例子:
2.1. 添加 jar 包
【mybatis】
mybatis-3.1.1.jar
【MYSQL 驱动包】
mysql-connector-java-5.1.7-bin.jar
2.2. 建库+表
create database mybatis;
use mybatis;
CREATE TABLE users(id INT PRIMARY KEY AUTO_INCREMENT, NAME
VARCHAR(20), age INT);
INSERT INTO users(NAME, age) VALUES('Tom', 12);
INSERT INTO users(NAME, age) VALUES('Jack', 11);
2.3. 添加 Mybatis 的配置文件conf.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="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
</configuration>
2.4. 定义表所对应的实体类
public class User {
private int id;
private String name;
private int age;
//get,set 方法
}
2.5. 定义操作 users 表的 sql 映射文件 userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ykq.mybatis_test.test1.userMapper">
<select id="getUser" parameterType="int" resultType="com.ykq.mybatis_test.test1.User">
select * from users where id=#{id}
</select>
</mapper>
2.6. 在 conf.xml 文件中注册 userMapper.xml 文件
<mappers>
<mapper resource="com/ykq/mybatis_test/test1/userMapper.xml"/>
</mappers>
2.7. 编写测试代码:执行定义的 select 语句
public class Test {
public static void main(String[] args) throws IOException {
String resource = "conf.xml";
//加载 mybatis 的配置文件(它也加载关联的映射文件)
Reader reader = Resources.getResourceAsReader(resource);
//构建 sqlSession 的工厂
SqlSessionFactory sessionFactory = new
SqlSessionFactoryBuilder().build(reader);
//创建能执行映射文件中 sql 的 sqlSession
SqlSession session = sessionFactory.openSession();
//映射 sql 的标识字符串
String statement = "com.atguigu.mybatis.bean.userMapper"+".selectUser";
//执行查询返回一个唯一 user 对象的 sql
User user = session.selectOne(statement, 1);
System.out.println(user);
}
}
3. 操作 users 表的 CRUD
3.1. XML 的实现
1). 定义 sql 映射 xml 文件:
<insert id="insertUser" parameterType="com.ykq.ibatis.bean.User">
insert into users(name, age) values(#{name}, #{age});
</insert>
<delete id="deleteUser" parameterType="int">
delete from users where id=#{id}
</delete>
<update id="updateUser" parameterType="com.ykq.ibatis.bean.User">
update users set name=#{name},age=#{age} where id=#{id}
</update>
<select id="selectUser" parameterType="int" resultType="com.ykq.ibatis.bean.User">
select * from users where id=#{id}
</select>
<select id="selectAllUsers" resultType="com.ykq.ibatis.bean.User">
select * from users
</select>
2). 在 config.xml 中注册这个映射文件
<mappers>
<mapper resource="com/ykq/mybatis_test/test1/userMapper.xml"/>
</mappers>
3). 在 dao 中调用:
4. 实际开发的模式
接口结合映射文件一起用。
1.创建一个接口
package com.ykq.dao;
import com.ykq.entity.Users;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author 闫克起
* @Date 2021/3/19 16:28
* @Version 1.0
*/
public interface UserDao {
/**
* 查询所有的用户信息
* @return 所有的用户信息
*/
public List<Users> selectAll();
/**
* 根据id查询用户信息
* @param
* @return
*/
//@Param:表示把该参数的名称作为映射文件的参数名
public Users selectByNameAndAge(@Param("name") String name,@Param("age") int age);
}
(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">
<!--namespace必须和接口的全路径匹配-->
<mapper namespace="com.ykq.dao.UserDao">
<!--id名称必须和接口中方法名一样-->
<select id="selectAll" resultType="com.ykq.entity.Users">
select * from users
</select>
<select id="selectByNameAndAge" resultType="com.ykq.entity.Users">
select * from users where name=#{name} and age=#{age}
</select>
</mapper>
4. 几个可以优化的地方
4.1. 连接数据库的配置单独放在一个 properties 文件中
## db.properties
<properties resource="db.properties"/>
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
4.2. 为实体类定义别名,简化 sql 映射 xml 文件中的引用
<typeAliases>
<typeAlias type="com.ykq.ibatis.bean.User" alias="_User"/>
</typeAliases>
5. 解决字段名与实体类属性名不相同的冲突
5.1. 准备表和数据:
CREATE TABLE orders(
order_id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(20),
order_price FLOAT
);
INSERT INTO orders(order_no, order_price) VALUES('aaaa', 23);
INSERT INTO orders(order_no, order_price) VALUES('bbbb', 33);
INSERT INTO orders(order_no, order_price) VALUES('cccc', 22);
5.2. 定义实体类:
public class Order {
private int id;
private String orderNo;
private float price;
}
5.3. 实现 getOrderById(id)的查询:
方式一: 通过在 sql 语句中定义别名
<select id="selectOrder" parameterType="int" resultType="_Order">
select order_id id, order_no orderNo,order_price price from orders where
order_id=#{id}
</select>
方式二: 通过<resultMap>
<select id="selectOrderResultMap" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="_Order" id="orderResultMap">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="price" column="order_price"/>
</resultMap>
6.实现关联表查询
6.1. 一对一关联
1). 提出需求
根据班级 id 查询班级信息(带老师的信息)
2). 创建表和数据
CREATE TABLE teacher(
t_id INT PRIMARY KEY AUTO_INCREMENT,
t_name VARCHAR(20)
);
CREATE TABLE class(
c_id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20),
teacher_id INT
);
ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id)
REFERENCES teacher(t_id);
INSERT INTO teacher(t_name) VALUES('LS1');
INSERT INTO teacher(t_name) VALUES('LS2');
INSERT INTO class(c_name, teacher_id) VALUES('bj_a', 1);
INSERT INTO class(c_name, teacher_id) VALUES('bj_b', 2);
3). 定义实体类:
public class Teacher {
private int id;
private String name;
}
public class Classes {
private int id;
private String name;
private Teacher teacher;
}
4). 定义 sql 映射文件 ClassMapper.xml
<!--
方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
封装联表查询的数据(去除重复的数据)
select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1
-->
<select id="getClass" parameterType="int" resultMap="ClassResultMap">
select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
</select>
<resultMap type="_Classes" id="ClassResultMap">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" column="teacher_id" javaType="_Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
<!--
方式二:嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型
SELECT * FROM class WHERE c_id=1;
SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的 teacher_id 的值
-->
<select id="getClass2" parameterType="int" resultMap="ClassResultMap2">
select * from class where c_id=#{id}
</select>
<resultMap type="_Classes" id="ClassResultMap2">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" column="teacher_id" javaType="_Teacher"
select="getTeacher">
</association>
</resultMap>
<select id="getTeacher" parameterType="int" resultType="_Teacher">
SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
</select>
5). 测试
@Test
public void testOO() {
SqlSession sqlSession = factory.openSession();
Classes c = sqlSession.selectOne("com.ykq.day03_mybatis.test5.OOMapper.getClass",
1);
System.out.println(c);
}
@Test
public void testOO2() {
SqlSession sqlSession = factory.openSession();
Classes c =
sqlSession.selectOne("com.ykq.day03_mybatis.test5.OOMapper.getClass2", 1);
System.out.println(c);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNz35QGz-1616202355105)(assets\1613702956028.png)]
6.2. 一对多关联
1). 提出需求
根据 classId 查询对应的班级信息,包括学生,老师
2). 创建表和数据:
CREATE TABLE student(
s_id INT PRIMARY KEY AUTO_INCREMENT,
s_name VARCHAR(20),
class_id INT
);
INSERT INTO student(s_name, class_id) VALUES('xs_A', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_B', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_C', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_D', 2);
INSERT INTO student(s_name, class_id) VALUES('xs_E', 2);
INSERT INTO student(s_name, class_id) VALUES('xs_F', 2);
3). 定义实体类:
public class Student {
private int id;
private String name;
}
public class Classes {
private int id;
private String name;
private Teacher teacher;
private List<Student> students;
}
4). 定义 sql 映射文件 ClassMapper.xml
<!--
方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集
SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id
AND c.c_id=1
-->
<select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and
c.c_id=#{id}
</select>
<resultMap type="_Classes" id="ClassResultMap3">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" column="teacher_id" javaType="_Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
<!-- ofType 指定 students 集合中的对象类型 -->
<collection property="students" ofType="_Student">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
<!--
方式二:嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型
SELECT * FROM class WHERE c_id=1;
SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的 teacher_id 的值
SELECT * FROM student WHERE class_id=1 //1 是第一个查询得到的 c_id 字段的值
-->
<select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
select * from class where c_id=#{id}
</select>
<resultMap type="_Classes" id="ClassResultMap4">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" column="teacher_id" javaType="_Teacher"
select="getTeacher2"></association>
<collection property="students" ofType="_Student" column="c_id"
select="getStudent"></collection>
</resultMap>
<select id="getTeacher2" parameterType="int" resultType="_Teacher">
SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
</select>
<select id="getStudent" parameterType="int" resultType="_Student">
SELECT s_id id, s_name name FROM student WHERE class_id=#{id}
</select>
5). 测试:
@Test
public void testOM() {
SqlSession sqlSession = factory.openSession();
Classes c = sqlSession.selectOne("com.ykq.day03_mybatis.test5.OOMapper.getClass3",
1);
System.out.println(c);
}
@Test
public void testOM2() {
SqlSession sqlSession = factory.openSession();
Classes c = sqlSession.selectOne("com.ykq.day03_mybatis.test5.OOMapper.getClass4",
1);
System.out.println(c);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFt9JOh2-1616202355108)(assets\1613703157685.png)]
7. 动态 SQL 与模糊查询
7.1 MyBatis动态语句分为4种元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBYWfLLw-1616202355110)(assets\1613703676781.png)]
7.2 Mybatis动态sql语句使用方式、例子
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` double DEFAULT NULL,
`isdeleted` tinyint(4) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
1、if元素,如下
<select id="selByName" resultType="yuan.yuanmybatis.entity.Account">
select id,name,created,updated from account where 1=1
<if test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</if>
</select>
2.choose
<select id="selByChoose" resultType="yuan.yuanmybatis.entity.Account">
select id,name,created,updated,money from account where 1=1
<choose>
<when test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</when>
<when test="money !=null and money !=''">
and money =#{money}
</when>
<otherwise>
and isdeleted=1
</otherwise>
</choose>
</select>
3.where元素,细心的读者会发现1、2点会有一个1=1,如果没有1=1,那么会变成如下错误语句:
select id,name,created,updated,money from account where and name like concat('%',#{name},'%')
这个时候我们可以用where元素处理sql:
<select id="selByName" resultType="yuan.yuanmybatis.entity.Account">
select id,name,created,updated from account
<where>
<if test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</if>
</where>
</select>
where元素在里面的条件成立的时候,才会加入where这个sql关键字
4.trim元素可以帮助我们去掉一下and、or等,prefix代表语句前缀, prefixOverrides代表要去掉的字符串
<select id="selByChoose" resultType="yuan.yuanmybatis.entity.Account">
select id,name,created,updated,money from account
<trim prefix="where" prefixOverrides="and">
<choose>
<when test="name !=null and name !=''">
and name like concat('%',#{name},'%')
</when>
<when test="money !=null and money !=''">
and money =#{money}
</when>
<otherwise>
and isdeleted=1
</otherwise>
</choose>
</trim>
</select>
5.set元素,它可以在遇到逗号的时候,把对应的逗号去掉
<update id="updateAccout" parameterType="yuan.yuanmybatis.entity.Account">
update account
<set>
<if test="name !=null and name !=''">
name=#{name},
</if>
<if test="money!=null and money!=''">
money=#{money}
</if>
</set>
where id=#{id}
</update>
6.foreach元素,它时一个循环语句,作用时用来遍历集合,支持数组、List、Set接口集合
<select id="selIn" resultType="yuan.yuanmybatis.entity.Account">
select id,name,created,updated from account where name in
<foreach collection="nameList" index="index" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select>
8. Mybatis 缓存
8.1 MyBatis 缓存理解
正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持
1. 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当
Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。
2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在
于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
3. 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存 Namespaces)的进行
了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
8.2. Mybatis 一级缓存
1) 提出需求:
根据 id 查询对应的用户记录对象
2). 准备数据库表和数据
CREATE TABLE c_user(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT
);
INSERT INTO c_user(NAME, age) VALUES('Tom', 12);
INSERT INTO c_user(NAME, age) VALUES('Jack', 11);
3). 创建表的实体类
public class User implements Serializable{
private int id;
private String name;
private int age;
}
4). userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.test8.userMapper">
<select id="getUser" parameterType="int" resultType="_CUser">
select * from c_user where id=#{id}
</select>
<update id="updateUser" parameterType="_CUser">
update c_user set
name=#{name}, age=#{age} where id=#{id}
</update>
</mapper>
5). 测试:
/*
* 一级缓存: 也就 Session 级的缓存(默认开启)
*/
@Test
public void testCache1() {
SqlSession session = MybatisUtils.getSession();
String statement = "com.ykq.mybatis.test8.userMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println(user);
/*
* 一级缓存默认就会被使用
*/
/*
user = session.selectOne(statement, 1);
System.out.println(user);
*/
/*
1. 必须是同一个 Session,如果 session 对象已经 close()过了就不可能用了
*/
/*
session = MybatisUtils.getSession();
user = session.selectOne(statement, 1);
System.out.println(user);
*/
/*
2. 查询条件是一样的
*/
/*
user = session.selectOne(statement, 2);
System.out.println(user);
*/
/*
3. 没有执行过 session.clearCache()清理缓存
*/
/*
session.clearCache();
user = session.selectOne(statement, 2);
System.out.println(user);
*/
/*
4. 没有执行过增删改的操作(这些操作都会清理缓存)
*/
/*
session.update("com.ykq.mybatis.test8.userMapper.updateUser",
new User(2, "user", 23));
user = session.selectOne(statement, 2);
System.out.println(user);
*/
}
8.3. Mybatis 二级缓存
1). 添加一个在 userMapper.xml 中
<mapper namespace="com.ykq.mybatis.test8.userMapper">
<cache/>
2) 在配置文件中开启二级缓存
<settings>
<setting name = "cacheEnabled" value = "true" />
</settings>
3). 测试
/*
* 测试二级缓存
*/
@Test
public void testCache2() {
String statement = "com.ykq.mybatis.test8.userMapper.getUser";
SqlSession session = MybatisUtils.getSession();
User user = session.selectOne(statement, 1);
session.commit();
System.out.println("user="+user);
SqlSession session2 = MybatisUtils.getSession();
user = session2.selectOne(statement, 1);
session.commit();
System.out.println("user2="+user);
}
``