流程
- 创建持久化类(POJO类)
- 编写持久化操作的Mapper文件,其中定义SQL语句
- 创建配置文件:连接哪种数据库、配置数据源、mappers文件路径
- 获取SqlSessionFactory和SqlSession,通过SqlSession操作数据库
- 关闭SqlSession
public class MyBatisTest
{
public static void main(String[] args)
{
//读取MyBatis的配置文件,其中包括连接哪种数据库,配置数据源,mappers文件路径等
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//获得Session实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = SqlSessionFactory.openSession();
//创建User
User user = new User("admin", "男", 26);
//插入数据,insert的第一个参数为UserMapper文件中id为save的动作,这里是<insert ..>
session.insert("org.test.mapper.UserMapper.save", user);
session.commit();
session.close();
}
}
详解
UserMapper
<mapper namespace="org.test.mapper.UserMapper">
<insert id="save" parameterType="org.test.domainUser" useGeneratedKeys="true">
INSERT INTO TB_USER(name, sex, age) VALUES(#{name}, #{sex}, #{age})
</insert>
</mapper>
id:用于在程序中引用该语句,也是session.insert()的第一个参数
parameterType:语句的接收参数,这里是我们定义的POJO类User
SqlSessionFactory
MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像
SqlSession
- MyBatis的关键对象,是执行持久化操作的对象,类似于JDBC中的Connection。
- 底层封装了JDBC连接,可用Session实例直接执行已映射的SQL语句
- 线程非安全对象,每个线程都应用自己的SqlSession
MyBatis配置文件结构
- properties:mapper文件中的"变量",可在Mapper文件的其它地方引用
- settings:MyBatis中极为重要的调整,会改变MyBatis的运行时行为,例如useGeneratedKeys允许JDBC自动生成主键
- typeAliases:为Java类型的全限定名设置别名,目的是更简短
- typeHandlers:类型处理器。无论是在预处理语句(PreparedStatement)中设置一个参数,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换为Java类型
- objectFactory:对象工厂,用于创建结果对象的新实例
- Environments:配置数据源
- Mapper映射器:指明Mapper文件在哪,进而找到其中的SQL语句
深入Mapper XML映射文件
这是MyBatis的魔力所在。跟具有相同功能的JDBC代码对比,将省掉约95%的代码。常用元素如下:
- select/insert/update/delete:分别映射CRUD语句
- sql:可被其它语句引用的可重用语句块
- cache:给定命名空间的缓存配置
- cache-ref:其它命名空间缓存配置的引用
- resultMap:最复杂和最强大的元素,描述如何从数据库结果集中加载对象
select
CRUD的使用类似,这里用select举例说明
<select id="selectUser" parameterType="int" resultType="hashmap">
SELECT * FROM TB_USER WHERE ID = #{id}
</select>
- id:命名空间中的唯一标识符,可以被用来引用这条语句
- parameterType:语句接收的参数类型。可省略,MyBatis可以通过TypeHandler推出来
- resultType:语句返回hash类型的对象,键是列名,值是结果行中的对应值,如{name=wxs, sex=男, grade=99}
- #{id}:占位符,类似于sql语句中的"?"
注意:若parameterType类型是自定义类型如User,则#{id/name}中的字段将会去查询User中的id/name
以上配置文件,在执行时会生成如下JDBC代码
String selectUser = "SELECT * FROM TB_USER WHERE ID=?";
Prepared ps = conn.preparedStatement(selectUser);
ps.setInt(1, id);
ResultMap
若使用resultType="map"的映射语句查询所有用户数据:
<select id="selectUser" resultType="map">
SELECT * FROM TB_USER
</select>
则结果看起来是这样的,查询语句的每一行都被封装成一个Map:
{name=wxs, sex=男, grade=99}
{name=bdm, sex=女, grade=98}
...
更好的方式是将resultType赋值为对应的User类型,此时MyBatis会将查询到的数据的列和需要返回的对象(User)的属性逐一进行匹配赋值,但如果两者有不一致的现象,则就不会进行自动赋值了。这时可以使用resultMap来处理。
首先,我们构造一个上述的不一致现象——POJO的属性和数据库表中的属性名字不同
//POJO类
public class User
{
private Integer id;
private String name;
private String sex;
private Integer age;
//省略get/set方法
}
//创建数据库
CREATE TABLE TB_USER2(
user_id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(18),
user_sex VARCHAR(18),
user_age INT
)
接着,我们在Mapper文件中利用resultMap完成这种“不一致”的映射
<resultMap id="userResultMap" type="org.test.domain.User">
<id property="id" column="user_id" />
<result property="name" column="user_name">
<result property="sex" column="user_sex">
<result property="age" column="user_age">
</resultMap>
可以看出,上述中间四条语句说明要将column映射到property,即数据库中的user_id/user_name/user_sex/user_age列映射到POJO对象的id/name/sex/age属性
在实际项目开发中,还有更加复杂的情况,例如执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时简单的映射已经无法解决问题,必须使用<resultMap.../>元素来完成“定制的”映射
举一个关联查询的例子
首先,创建两个表,并通过外键关联。
CREATE TABLE TB_CLAZZ(
id INT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(18)
)
CREATE TABLE TB_STUDENT(
user_id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(18),
user_sex VARCHAR(18),
user_age INT,
clazz_id INT,
FOREIGN KEY (clazz_id) REFERENCES TB_CLAZZ(id)
)
接着,创建对应的POJO(省略get/set)
public class Clazz
{
private Integer id;
private String code;
}
public class Student
{
private Integer id;
private String name;
private String sex;
prvate Integer age;
//关联的Clazz对象
private Clazz clazz;
}
接着,编辑对应的Mapper文件
<!-- 映射学生对象的resultMap -->
<resultMap id="studentResultMap" type="org.test.domain.Student">
<id property="id" column="id"/>
<result property="name", column="name"/>
<result property="sex", column="sex"/>
<result property="age", column="age"/>
<!-- 关联映射:根据clazz_id查询到Clazz对象并完成映射 -->
<association property="clazz" column="clazz_id"
javaType="org.test.domain.Clazz"
select="selectClazzWithId"/>
</resultMap>
<!-- 根据班级Id查询班级 -->
<select id="selectClazzWithId" resultType="org.test.domain.Clazz">
SELECT * FROM TB_CLAZZ where id = #{id}
</select>
<!-- 查询所有学生信息,注意返回类型是resultMap -->
<select id="selectStudent" resultMap="studentResultMap">
SELECT * FROM TB_STUDENT
<select>
<association>各元素说明
property:Student的属性名
column:数据表的列名
javaType:概属性对应的名称
select:依据clazz_id执行查询语句,将结果封装到property对应的属性中
最后,在程序中执行关联查询
List<Student> list = session.selectList("org.test.mapper.UserMapper.selectStudent")
查询结果类似:
Student [id = 1, name=wxs, sex=男, age=28, clazz=Clazz[id=1, code=j1601]]
Student [id = 2, name=bdm, sex=女, age=28, clazz=Clazz[id=1, code=j1601]]
类似的,如果在查询班级的同时也要查询出该班的所有学生,此时会用到<collection>标签,不详记录啦
MyBatis关联映射
一对一
一对一关系映射:比如一个人只能有一个身份证,一个身份证只能给一个人用。推荐使用唯一(unique)主外键关联:
CREATE TABLE tb_card(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18)
);
CREATE TABLE tb_person(
id INT PRIMARY KEY AUTO_INCREMENT,
...,
cart_id INT UNIQUE,
FOREIGN KEY (card_id) REFERENCES tb_card(id)
);
mapper文件的编辑和POJO都类似上边的Student和Clazz,同样利用<association>完成关联查询,不写出来啦
- mapper接口对象
注意,之前测试时均使用SqlSession对象调用insert/update/delete/select测试,但官方建议通过mapper接口的代理对象访问mybatis,该对象关联了SqlSession对象,可通过该对象直接调用方法操作数据库。
下面定义一个mapper接口对象,需要注意的是,mapper接口对象的类名和之前的XML文件中的mapper的namespace一致,而方法名和参数也必须和XML文件中的<select/update.../>元素的id属性和parameterType属性一致
<!-- mapper文件 -->
<mapper namespace="org.test.mapper.PersonMapper">
<select id="selectPersonById" parameterType="int" .../>
</mapper>
//mapper接口对象
public interface PersonMapper{
Person selectPersonById(Integer id);
}
//测试
//读取MyBatis的配置文件,其中包括连接哪种数据库,配置数据源,mappers文件路径等
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//获得Session实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = SqlSessionFactory.openSession();
//获得mapper接口的代理对象
PersonMapper personMapper = session.getMapper(PersonMapper.class);
Person p = personMapper.selectPersonById(1);
session.commit();
session.close();
多对一
上面讲解resultMap时的clazz和student就是,就是多对一关系:一个学生只属于一个班级,一个班级可以有多个学生。多对一关系推荐使用主外键关联(与一对一的区别是没有unique)。
具体例子参考讲解resultMap时的clazz和student即可,通过<association>和<collection>完成映射。此外,了解一个概念,在执行关联查询时可配置立即加载或懒加载,即查询班级信息时,立即查询出相关学生信息或者在需要操作相关学生信息时,再去执行对应的关联查询。书中例子,一般情况下,一对多的关系时都使用懒加载
多对多
订单和商品就是多对多的关系:一个订单中可以购买多种商品,一种商品也可以属于多个不同的订单。多对多关系建议使用一个中间表来维护,中间表的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。例子树上有
小结
- A和B是一/多对一关系时,则类A的数据结构中包含对象B,对应Mapper文件中使用<association>
- A和B是一/多对多关系时,则类A的数据结构汇中包含List<B>,对应Mapper文件中使用<collection>
动态SQL
解决了根据不同条件拼接SQL语句的问题
MyBatis采用ONGL的表达式完成动态SQL
常用元素有if、choose(when、otherwise)、where、set、foreach、bind,接下来介绍如何在Mapper文件中使用。
if
<mapper namespace="...EmployeeMapper">
<select id="selectEmployee" resultType=...>
SELECT * FROM tb_employee WHERE state = 'ACTIVE'
<if test="id != null">
and id = #{id}
</if>
</mapper>
说明:
1、<if>条件表明如果传入了id则还需通过id查询
2、#{id}获取参数有两种方式:从JavaBean中获取或者从HashMap中获取,即对应的mapper接口对象的selectEmployee方法的入参应为javaBean或HashMap,例如
public interface EmployeeMapper{
List<Employee> selectEmployee(HashMap<String, Object> params)
}
choose(when、otherwise)
choose...when...otherwise处理逻辑类似switch...case...default
<select ...>
SELECT * FROM tb_employee WHERE state = 'ACTIVE'
<choose>
<when test = "id != null">
and id = #{id}
</when>
<when test="loginname != null and password != null">
and loginname = #{loginname} and password = #{password}
</when>
<otherwise>
and sex='男'
</otherwise>
</choose>
</select>
上述<select>表明:
- 当id != null时,则根据id查询;
- 否则,当loginame != null and password != null时,根据loginname和password查询;
- 否则,根据sex='男'查询
任何一个条件满足后,就不再匹配后续的<when>或<otherwise>了
where
先看以下如果没有where,则下面的<select>会有什么问题
<select...>
SELECT * FROM tb_employee WHERE
<if test="state != null ">
state = #{state}
</if>
<if test=" id != null ">
and id= #{id}
<if/>
</select>
如果执行上述select时,如果没有传入任何参数,则sql语句如下
SELECT * FROM tb_employee WHERE
如果执行上述select时,如果只传入id,则sql语句如下
SELECT * FROM tb_employee WHERE and id = #{id}
使用<where>后,它知道如何纠正上述错误,是否需要where字句,是否需要去掉and/or
SELECT * FROM tb_employee
<where>
<if test=" state != null ">
state = #{state}
</if>
<if test=" id != null ">
and id= #{id}
<if/>
</where>
set
用于更新语句:动态取舍需要更新的列
<update ...>
UPDATE tb_employee
<set>
<if test="loginname != null">loginname=#{loginname},</if>
<if test="password != null">password=#{password}</if>
</set>
</update>
说明
- <set>会自动处理好逗号
- 对应的Mapper接口对象的对应更新方法,入参为JavaBean(通常时之前查询出来的JavaBean),如
void updateEmployee(Employee employee)
foreach
对一个集合遍历,通常用于构建IN条件语句时
<select>
SELECT * FROM tb_employee WHERE ID in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
对应mapper接口对象的对应方法的入参应该为一个id的List
bind
从OGNL表达式中创建一个变量,将其绑定到上下文
<select id="selectEmployeeLikeName" ...>
<bind name="pattern" value="'%' + _parameter.getName() + '%'"" />
SELECT * FROM tb_employee WHERE loginname LIKE #{pattern}
</select>
...
//查询loginname包含"o"的所有员工信息
Employee employee = new Employee();
employee.setName("o");
List<Employee> list = employeeMapper.selectEmployeeLikeName(employee)
笔记总结于《Spring MVC+MyBatis企业应用实战》