Mybatis详解
原文地址:Mybatis详解
写在最前
个人的学习笔记,如果有错误的地方,请各位大佬指出orz
概述
Mybatis是一个优秀的基于Java的持久层框架,它内部封装了Jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由Mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对Jdbc进行了封装,屏蔽了Jdbc Api底层访问细节,使我们不用与Jdbc Api打交道,就可以完成对数据库的持久化操作。
Mybatis开源
https://github.com/mybatis/mybatis-3
Mybatis文档
https://mybatis.org/mybatis-3/zh/index.html
入门
Mybatis需要两个配置,一个是关于总体的配置,一个是具体的每个Dao的映射配置。下面用一个简单的Demo来演示下,具体的参数含义后面再说,先总体上感受一下。
总配置:SqlMapConfig.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://127.0.0.1:3306/chayedan_mybatis?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</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="user">
<select id="findAll" resultType="com.chayedan.domain.User">
select * from user
</select>
</mapper>
User类
package com.chayedan.domain;
import java.util.Date;
public class User {
private int id;
private String username;
private String gender;
private Date birthday;
private String address;
private String order;
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", gender='" + gender + '\'' +
", birthday=" + birthday +
", address='" + address + '\'' +
'}';
}
}
测试类
public class AppTest {
@Test
public void testFindAll() {
//创建一个sqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = builder.build(AppTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"));
//创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行 sql语句
List<User> list = sqlSession.selectList("user.findAll");
list.forEach(u-> System.out.println(u));
}
}
映射配置中的mapper标签的命名空间需要注意下
<mapper namespace="user">
跟在测试类中的的selectList中的参数是对应的
List<User> list = sqlSession.selectList("user.findAll");
意思就是将命名空间为user的mapper中的id为findAll的标签传递过去
CRUD
查询
上面的入门例子已经演示过了,下面再演示一个
<select id="findById" parameterType="int" resultType="com.chayedan.domain.User">
select * from user where id=#{id}
</select>
User user = sqlSession.selectOne("user.findById", 2);
比较特殊点的排序例子
<select id="findByLike1" parameterType="com.chayedan.domain.User" resultType="com.chayedan.domain.User">
select * from user where username like ${username} order by ${order} desc
</select>
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("'%王%'");
user.setOrder("id");
List<User> list = sqlSession.selectList("user.findByLike1", user);
主要注意的就是#{}和${}的区别,详情请见备注
查询有两个常用的方法
selectList
selectOne
:也是用selectList
实现的,就是在返回的结果那做了一个判断,结果数大于一就报错。。。
增加
<insert id="save" parameterType="com.chayedan.domain.User" >
<!--
last_insert_id() 用来获取此次插入的自增长id 的值
keyProperty:用来表示插入的id的值 赋值给对象的哪个属性
order:是在下面的语句之后还是之前执行
-->
<selectKey resultType="int" keyProperty="id" order="AFTER">
select last_insert_id()
</selectKey>
insert into user VALUES (null,#{username},#{gender},#{birthday},#{address})
</insert>
User user = new User();
user.setUsername("小王");
user.setAddress("北京");
user.setBirthday(new Date());
user.setGender("1");
int insert = sqlSession.insert("user.save", user);
System.out.println("影响的行数"+insert);
System.out.println(user);
sqlSession.commit();
sqlSession.close();
insert
方法,返回值为成功插入的行数,需要注意的是,Mybatis自动提交是默认关闭的
所以需要sqlSession.commit();
来手动提交
另外select last_insert_id()
是最后一次插入后ID的属性值,通过keyProperty
,已经将ID属性赋值到传过来的user对象中了,通过System.out.println(user);
就能看到最后一次插入的user对象的ID属性
更改
<update id="updateUsernameById" parameterType="com.chayedan.domain.User">
update user set username=#{username} where id=#{id}
</update>
User user = new User();
user.setUsername("小蓝");
user.setId(9);
int i = sqlSession.insert("user.updateUsernameById", user);
sqlSession.commit();
sqlSession.close();
删除
<delete id="delById" parameterType="int" >
DELETE from user where id=#{id}
</delete>
sqlSession.delete("user.delById",9);
sqlSession.commit();
sqlSession.close();
备注
#{}和${}的问题
-
#{}
#{}等同于PreparedStatement中的占位符?,会自动对传入的字符串数据加一对单引号,可以避免Sql注入。比如
select * from user where username = #{username}
,传入的username为小张,那么最后打印出来的就是select * from user where username = ‘小张’
#{}可以接收简单类型值或Pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是任意名称。
-
${}
${}将传入的数据直接显示生成在Sql中,只是简单的拼接。如:order by i d , 如 果 传 入 的 值 是 i d , 则 解 析 成 的 S q l 为 o r d e r b y i d 。 如 果 上 面 的 例 子 使 用 {id},如果传入的值是id,则解析成的Sql为order by id。如果上面的例子使用 id,如果传入的值是id,则解析成的Sql为orderbyid。如果上面的例子使用{},则成了select * from user where username = 小张
真正的开始
写了上面一堆,是不是发现没什么卵用。还不是要自己写实现(AppTest里面的前面一堆)
有一说一,确实从刚才看来没什么用
但是
刚才只是铺垫,现在来真正的开始使用Mybatis
既然有大量的重复代码,而我们的核心是SQL语句和查询返回的结果,那么自然,我们就想到了动态代理
Mybatis使用动态代理技术,来让我们面向接口编程
DEMO
这是UserDao接口
public interface UserDao {
User findById(int id);
List<User> findAll();
}
这是配置文件
<?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.chayedan.dao.UserDao">
<select id="findAll" resultType="com.chayedan.domain.User">
select * from user
</select>
<select id="findById" parameterType="int" resultType="com.chayedan.domain.User">
select * from user where id=#{id}
</select>
</mapper>
测试类
@Test
public void testFindById() {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sqlSessionFactory = builder.build(AppTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用getMapper 可以获取一个该接口的实现类对象 底层采用动态代理技术
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findById(1);
System.out.println(user);
sqlSession.close();
}
这样Mybatis就自动地帮你完成了一个实现类,通过使用getMapper
方法
不过需要注意的是,这种技术必须保证四个一样
- 必须保证接口的类的全限定名,跟配置文件中的 namespace一样
- 方法名 必须跟配置文件中 id一致
- 参数必须配置声明参数一样
- 返回必须跟配置文件声明一样
核心配置文件的相关属性
在入门中的那个配置文件其实已经差不多了,下面的都是一些有可能会用到的属性
typeAlias(别名)
如果嫌全限定名麻烦,那么可以使用别名的方式,有两种
方式一
<typeAliases>
<typeAlias type="com.chayedan.domain.User" alias="user"></typeAlias>
</typeAliases>
这种就是你可以在resultType
的地方使用user来代理全限定名
方式二
<typeAliases>
<!--<typeAlias type="com.chayedan.domain.User" alias="user"></typeAlias>-->
<package name="com.chayedan.domain"></package>
</typeAliases>
如果使用的是package这种方式,它会扫描包下的所有实体类,每个实体类的别名都是类名的小写形势。
Mapper配置文件的相关属性
在入门中的那个配置文件其实也已经差不多了,下面的都是一些有可能会用到的属性
resultMap(结果集映射)
<!--
结果集映射:
id: 为了结果映射起个id
type: 为了哪个实体类
-->
<resultMap id="userMap" type="user">
<!--
property 声明jaavBean属性名
column 声明数据库查询出来的结果集对应列名 (考虑别名)
-->
<result property="id" column="uid"></result>
<result property="username" column="name"></result>
<result property="gender" column="gender"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
</resultMap>
<select id="findById1" parameterType="int" resultMap="userMap">
select id AS uid,NAME,gender,birthday,address from user where id=#{id}
</select>
也就是将查出来列属性按规定映射到实体类上面
type就是对应的实体类,这里写user是因为前面核心配置中启用了别名。
最后将resultType改为resultMap,使用resultMap的id指定结果集映射的形式
传递多个参数的方法(parameterType)
将参数封装为JavaBean对象进行多个参数的传递
<select id="findByUsernameAndAddress" parameterType="user" resultMap="userMap">
select id AS uid,NAME,gender,birthday,address from user where name=#{username} AND address=#{address}
</select>
如果是复杂对象,可以用.
来使用复合对象中的属性
<select id="findByCondition2" parameterType="productQuery" resultType="product">
SELECT * FROM product WHERE cid=(
SELECT cid FROM category WHERE cname=#{category.cname}
)
AND cicun=#{product.size}
</select>
ProductQuery类中包含了Product类和Category类
Product类中有size属性
Category类中有cname属性
if标签和where标签(传递不定长参数)
跟Java中的if一样,test是必须的,等号后面的字符串是判断条件
<select id="findByCondition" parameterType="user" resultMap="UserMap">
select * from user where 1=1
<if test="username!=null">
and name=#{username}
</if>
<if test="address!=null">
and address=#{address}
</if>
</where>
</select>
where标签则是整理where里的条件,可以帮助去掉第一个and,这样不需要 where 1=1
<select id="findByCondition" parameterType="user" resultMap="UserMap">
select * from user
<where>
<if test="username!=null">
and name=#{username}
</if>
<if test="address!=null">
and address=#{address}
</if>
</where>
</select>
foreach标签
如果根据多个id查询用户信息,就需要传递一个集合进去拼接sql语句,这时候就需要用到foreach标签。
为了好解释,写一段Java代码对应
list list=new arraylist();
list.add(1);
list.add(2);
list.add(4);
list.add(6);
list.add(9);
String sql=" select * from user where"
sql+=" id in( "
for(int i:list){
sql+=i+","
}
sql=sql.substring(0,sql.length()-1);
sql+=")"
xml中的写法
<select id="findByIds1" parameterType="list" resultMap="UserMap">
select * from user
<where>
<foreach collection="list" separator="," item="x" close=")" open="id in(">
#{x}
</foreach>
</where>
</select>
collection:指明你遍历是什么属性
- list集合——list
- 数组——array
- 复合查询对象——属性名
separator:分隔符,即java代码中的11行中的那个加号后面的逗号
item:遍历项,即java代码中第十行的int i的i,跟java一样,随便叫什么都可以
close:结束添加项,即最后你要拼接上什么,即第十四行
open:开始添加项,即拼接开始前需要以什么开头,即第九行
下面是复合查询的例子
复合查询类(主要是属性名一定要对应上)
public class QueryObject {
private List<Integer> ids;
private String address;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
xml文件的写法
<select id="findByIdsAndAddress" parameterType="queryObject" resultMap="UserMap">
select * from user
<where>
<foreach collection="ids" separator="," item="x" close=")" open="id in(">
#{x}
</foreach>
and address=#{address}
</where>
</select>
collection="ids"中的ids要和类中的private List ids 属性名对应
注意:这里写parameterType="queryObject"是因为queryObject我已经用了别名,如果没有用别名,需要写全限定名
association标签
使用在多表查询时,两个表的查询对象是一对一关系的数据
<resultMap id="userWithInfo" type="user">
<result property="id" column="id"></result>
<result property="username" column="name"></result>
<result property="gender" column="gender"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
<association property="userInfo" javaType="userInfo">
<result property="uid" column="uid"></result>
<result property="hobbies" column="hobbies"></result>
<result property="email" column="email"></result>
</association>
</resultMap>
<select id="findByIdWithInfo" parameterType="int" resultMap="userWithInfo">
SELECT * FROM USER u,userinfo ui WHERE u.`id`=ui.`uid` AND u.id=#{id}
</select>
association:剩下部分数据与主体的关系
property:关联的属性名,对应下面的变量名userInfo
javaType:关联的属性类型,对应下面的类名UserInfo
public class User {
private int id;
private String username;
private String gender;
private Date birthday;
private String address;
private String order;
private UserInfo userInfo;
}
collection标签
使用在多表查询时,两个表的查询对象是一对多关系或者多对多的关系时
<resultMap id="userWithOrders" type="user">
<result property="id" column="id"></result>
<result property="username" column="name"></result>
<result property="gender" column="gender"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
<collection property="orders" ofType="order">
<result property="oid" column="oid"></result>
<result property="desc" column="desc"></result>
<result property="price" column="price"></result>
<result property="createTime" column="createTime"></result>
<result property="uid" column="uid"></result>
</collection>
</resultMap>
collection:数据与主体关联关系
property:主体中属性名,即 collection 标签的property对应下面的oeders。
ofType:集合中的对象的类型,即 collection 标签的ofType对应下面的Order。
private List<Order> orders;
sql标签
统一定义字段的,因为在开发中最好不要用select * ,需要自己定义字段名
<sql id="userFields">
id,username,gender,birthday,address
</sql>
相当于userFields=id,username,gender,birthday,address
那么就可以在其他标签中使用
<select id="findAll" resultType="com.chayedan.domain.User">
select
<include refid="userFields"/>
from user
</select>
跟以前的作用是一样的
如果要引入其他Mapper的sql标签,可以使用全限定名,不过还不如在上面自己再写一个sql标签,免得写这么长的全限定名
<select id="findUsersWithOrder" resultMap="userWithOrders">
SELECT
<include refid="userFields"/>,
<include refid="com.chayedan.dao.OrderDao.orderFields"/>
FROM USER u LEFT JOIN orders o ON u.id=o.uid
</select>