Mybatis
1.Mybatis 介绍
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录. 半自动化框架。必须写sql语句。
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.cd.ibatis.bean.User">
select * from users where id=#{id}
</select>
<select id="selectAllUsers" resultType="com.cd.ibatis.bean.User">
select * from users
</select>
2). 在 config.xml 中注册这个映射文件
<mappers>
<mapper resource="com/cd/mybatis_test/test1/userMapper.xml"/>
</mappers>
3). 在 dao 中调用:
User selectAllUsers();
User selectUser(int id);
int insertUser(User user);
int deleteUser(int id);
int updateUser(User user);
4. 实际开发的模式
接口结合映射文件一起用。
1.创建一个接口
package com.cd.dao;
import com.cd.entity.Users;
import org.apache.ibatis.annotations.Param;
import java.util.List;
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>
4.3. 可以在 src 下加入 log4j 的配置文件打印日志信息
1. 添加 jar:
log4j-1.2.16.jar
2.1. log4j.properties(方式一)
log4j.properties,
log4j.rootLogger=DEBUG, Console
#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
2.2. log4j.xml(方式二)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="debug" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
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. 一对一关联
根据班级 id 查询班级信息(带老师的信息)
1). 创建表和数据
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);
2). 定义实体类:
public class Teacher {
private int id;
private String name;
}
public class Classes {
private int id;
private String name;
private Teacher teacher;
}
3). 定义 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>
4). 测试
@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);
}
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);
}
7. 动态 SQL 与模糊查询
7.1 MyBatis动态语句分为4种元素:
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);
*/
}
9. pagehelper分页插件
9.1 使用 PageHelper
你只需要在 classpath 中包含 pagehelper-x.y.z.jar 和 jsqlparser-x.y.z.jar。
9.2 在 MyBatis 配置 xml 中配置拦截器插件
- 2 在 MyBatis 配置 xml 中配置拦截器插件
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
9.3 使用PageInfo的用法
//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());