返回

SSM框架04-MyBatis

MyBatis入门

MyBatis简介

mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力 去处理加载驱动、创建连接、创建statement等繁杂的过程。mybatis通过xml或注解的方式将要执行的各种 statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。

最后mybatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

MyBatis快速入门

MyBatis官网地址:http://www.mybatis.org/mybatis-3/

开发步骤

  • 添加MyBatis的坐标
  • 创建user数据表
  • 编写User实体类
  • 编写映射文件UserMapper.xml
  • 编写核心文件SqlMapConfig.xml
  • 编写测试类

映射文件概述

增删改查操作

插入操作注意问题

  • 插入语句使用insert标签
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • Sql语句中使用#{实体属性名}方式引用实体中的属性值
  • 插入操作使用的API是sqlSession.insert(“命名空间.id”,实体对象);
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

修改操作注意问题

  • 修改语句使用update标签
  • 修改操作使用的API是sqlSession.update(“命名空间.id”,实体对象);

删除操作注意问题

  • 删除语句使用delete标签
  • Sql语句中使用#{任意字符串}方式引用传递的单个参数
  • 删除操作使用的API是sqlSession.delete(“命名空间.id”,Object)

核心配置文件概述

核心配置文件层级关系

  • configuartion配置
    • properties属性
    • settings设置
    • typeAliases类型别名
    • typeHandlers类型处理器
    • objectFactory对象工厂
    • plugins插件
    • environments环境
      • environment环境变量
        • transactionManager事务管理器
        • dataSource数据源
    • databaseidProvider数据库厂商标识
    • mappers映射器
environments标签

其中,事务管理器(transactionManager)类型有两种:

  • JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

其中,数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
  • JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
mapper标签

该标签的作用是加载映射的,加载方式有如下几种:

  • 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  • 使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
  • 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
  • 将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>
properties标签

实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件

typeAliases标签

类型别名是为Java 类型设置一个短的名字。

【例】原来的类型名称配置如下:

1
2
3
<select id="findAll" resultType="one.wangyi.domain.User">
	select * from User
</select>

配置typeAliases,为one.wangyi.domain.User定义别名为user

1
2
3
<typeAliases>
	<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>
</typeAliases>

配置别名后就可以使用了:

1
2
3
<select id="findAll" resultType="user">
	select * from User
</select>

上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名:

别名 数据类型
string String
long Long
int Integer
double Double
boolean Boolean
…… ……

MyBatis相应API

SqlSessionFactoryBuilder

通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象

1
2
3
4
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

SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个:

方法 解释
openSession() 会默认开启一个事务,但事务不会自动提交,也就意味着需要手动提 交该事务,更新操作数据才会持久化到数据库中
openSession(boolean autoCommit) 参数为是否自动提交,如果设置为true,那么不需要手动提交事
SqlSession会话对象

SqlSession 实例在 MyBatis 中是非常强大的一个类。能看到所有执行语句、提交或回滚事务和获取映射器实例的方法。

执行语句的方法主要有:

1
2
3
4
5
<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)

操作事务的方法主要有:

1
2
void commit()
void rollback()

Mybatis的Dao层实现

传统开发方式

项目中有dao层接口类及其实现类,将mybatis代码写入dao层实现类的方法中,在service层调用dao层实现类中的方法获得数据库中数据

代理开发方式

采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是企业的主流。

Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:

  • Mapper.xml文件中的namespace与mapper接口的全限定名相同
  • Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
  • Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
  • Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

代理方式对Dao进行实现:

1
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

MyBatis的映射文件

MyBatis映射文件配置:

  • <select>:查询
  • <insert>:插入
  • <update>:修改
  • <delete>:删除
  • <where>:where条件
  • <if>:if判断
  • <foreach>:循环
  • <sql>:sql片段抽取

动态sql语句

概述

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

参考的官方文档,描述如下:

动态 SQL 之if

我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<select id="findByCondition" parameterType="User" resultType="user">
    select * from user
    <where>
        <if test="id!=0">
            and id = #{id}
        </if>
        <if test="username!=null">
            and username = #{username}
        </if>
        <if test="password!=null">
            and password = #{password}
        </if>
    </where>
</select>

动态 SQL 之foreach

循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)

1
2
3
4
5
6
7
8
<select id="findByIds" parameterType="list" resultType="user">
    select * from user
    <where>
        <foreach collection="list" open="id in(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>

<foreach>标签用于遍历集合,它的属性:

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符

SQL片段抽取

Sql 中可将重复的 sql 提取出来,使用时用 <include> 引用即可,最终达到 sql 重用的目的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!--抽取sql片段-->
<sql id="selectUser">select * from user</sql>

<select id="findByCondition" parameterType="User" resultType="user">
    <include refid="selectUser"/> 
    <where>
        <if test="id!=0">
            and id = #{id}
        </if>
        <if test="username!=null">
            and username = #{username}
        </if>
        <if test="password!=null">
            and password = #{password}
        </if>
    </where>
</select>

MyBatis的核心配置文件

MyBatis核心配置文件常用标签:

  • properties标签:该标签可以加载外部的properties文件
  • typeAliases标签:设置类型别名
  • environments标签:数据源环境配置标签
  • typeHandlers标签:配置自定义类型处理器
  • plugins标签:配置MyBatis的插件

typeHandlers标签

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器(截取部分):

可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler, 然 后可以选择性地将它映射到一个JDBC类型。

例如需求:一个Java中的Date数据类型,想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的bigint毫秒值之间转换。

开发步骤:

  • 定义转换类继承类BaseTypeHandler
  • 覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法
  • 在MyBatis核心配置文件中进行注册
  • 测试转换是否正确

plugins标签

MyBatis可以使用第三方的插件来对功能进行扩展。

分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据

开发步骤:

  • 导入通用PageHelper的坐标
  • 在mybatis核心配置文件中配置PageHelper插件
  • 测试分页数据获取

MyBatis的多表操作

一对一查询

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

一对一查询的模型

一对一查询的sql语句

对应的sql语句:

1
SELECT * FROM orders,user where orders.uid=user.id;

查询的结果如下:

创建实体类

创建Order和User实体:

创建接口类

创建OrderMapper接口:

1
2
3
public interface OrderMapper {
	List<Order> findAll();
}

配置映射文件

配置OrderMapper.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<mapper namespace="one.wangyi.mapper.OrderMapper">
    <resultMap id="orderMap" type="order">
        <!--手动指定字段与实体属性的映射关系
            column: 数据表的字段名称
            property: 实体的属性名称
        -->
        <id column="id" property="id"></id>
        <result column="ordertime" property="ordertime"></result>
        <result column="total" property="total"></result>
        <result column="uid" property="user.id"></result>-->
        <result column="username" property="user.username"></result>-->
        <result column="password" property="user.password"></result>-->
        <result column="birthday" property="user.birthday"></result>-->
    </resultMap>
    <select id="findAll" resultMap="orderMap">
        SELECT * FROM orders,user where orders.uid=user.id
    </select>
</mapper>

其中还可以配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<resultMap id="orderMap" type="order">
    <!--手动指定字段与实体属性的映射关系
            column: 数据表的字段名称
            property: 实体的属性名称
        -->
    <id column="id" property="id"></id>
    <result column="ordertime" property="ordertime"></result>
    <result column="total" property="total"></result>
    <!--
            property: 当前实体(order)中的属性名称(private User user)
            javaType: 当前实体(order)中的属性类型(User)
        -->
    <association property="user" javaType="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="birthday" property="birthday"></result>
    </association>
</resultMap>

测试结果

1
2
3
4
5
6
// 获得MyBatis框架生成的OrderMapper接口的实现类
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> orderList = mapper.findAll();
for (Order order:orderList){
    System.out.println(order);
}

一对多查询

一对多查询的模型

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

一对多查询的sql语句

对应的sql语句:

1
SELECT * FROM user,orders WHERE user.id=orders.uid;

查询的结果如下:

修改实体类

创建接口类

创建UserMapper接口:

1
2
3
public interface UserMapper {
	List<User> findAll();
}

配置映射文件

配置UserMapper.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<mapper namespace="one.wangyi.mapper.UserMapper">
    <resultMap id="userMap" type="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="birthday" property="birthday"></result>
        <!--配置集合信息
            property:集合名称
            ofType:当前集合中的数据类型
        -->
        <collection property="orderList" ofType="order">
            <!--封装order的数据-->
            <id column="id(1)" property="id"></id>
            <result column="ordertime" property="ordertime"></result>
            <result column="total" property="total"></result>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="userMap">
        SELECT *FROM user,orders WHERE user.id=orders.uid
    </select>
</mapper>

测试结果

1
2
3
4
5
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
for (User user:userList) {
    System.out.println(user);
}

多对多查询

多对多查询的模型

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用

多对多查询的需求:查询用户同时查询出该用户的所有角色

多对多查询的语句

对应的sql语句:

1
select * from user,user_role,role where user.id=user_role.user_id AND user_role.role_id=role.id;

查询结果如下:

创建实体类

创建Role实体,修改User实体:

创建接口类

添加UserMapper接口方法:

1
2
3
4
public interface UserMapper {
    public List<User> findAll();
    public List<User> findUserAndRoleAll();
}

配置映射文件

配置UserMapper.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<mapper>
    <resultMap id="userRoleMap" type="user">
        <!--user的信息-->
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="birthday" property="birthday"></result>
        <!--user内部的roleList信息-->
        <collection property="roleList" ofType="role">
            <id column="role_Id" property="id"></id>
            <result column="roleName" property="roleName"></result>
        </collection>
    </resultMap>

    <select id="findUserAndRoleAll" resultMap="userRoleMap">
        select * from user,user_role,role where user.id=user_role.user_id AND user_role.role_id=role.id
    </select>
</mapper>

测试结果

1
2
3
4
5
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userAndRoleAll = mapper.findUserAndRoleAll();
for (User user : userAndRoleAll) {
    System.out.println(user);
}

小结

MyBatis多表配置方式:

  • 一对一配置:使用<resultMap>做配置
  • 一对多配置:使用<resultMap>+<collection>做配置
  • 多对多配置:使用<resultMap>+<collection>做配置

Mybatis的注解开发

MyBatis的常用注解

Mybatis可以使用注解开发方式,这样就可以减少编写Mapper映射文件了

  • @Insert:实现新增
  • @Update:实现更新
  • @Delete:实现删除
  • @Select:实现查询
  • @Result:实现结果集封装
  • @Results:可以与@Result 一起使用,封装多个结果集
  • @One:实现一对一结果集封装
  • @Many:实现一对多结果集封装

修改MyBatis的核心配置文件,因为使用了注解来替代映射文件,所以只需要加载使用了注解的Mapper接口即可

1
2
3
4
<mappers>
	<!--扫描使用注解的类-->
	<mapper class="one.wangyi.mapper.UserMapper"></mapper>
</mappers>

或者指定扫描含映射关系的接口所在的包也可以

1
2
3
4
5
<!--加载映射关系-->
<mappers>
    <!--指定接口所在的包-->
    <package name="one.wangyi.mapper"></package>
</mappers>

之前我们可以在映射文件中通过配置<resultMap>来实现复杂关系映射,在使用注解开发后,我们可以使用@Results注解 ,@Result注解,@One注解,@Many注解组合完成复杂关系的配置。

  • @Results:代替的是标签<resultMap>,该注解中可以使用单个@Result注解,也可以使用@Result集合。使用格式:@Results( { @Result(), @Result() } )@Results( @Result() )
  • @Result:代替了<id>标签和<result>标签,@Result中属性介绍:
    • column:数据库的列名
    • property:需要装配的属性名
    • one:需要使用的@One 注解@Result ( one=@One( ) )
    • many:需要使用的@Many 注解@Result ( many=@Many( ) )
  • One(一对一):代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
    • select:指定用来多表查询的 sqlmapper
      • 使用格式:@Result(column=" ",property="",one=@One(select=""))
  • Many(多对一):代替了<collection>标签, 是多表查询的关键,在注解中用来指定子查询返回对象集合。
    • 使用格式:@Result(property="",column="",many=@Many(select=""))

一对一查询

查询一个订单,与此同时查询出该订单所属的用户

一对一查询的语句

对应的sql语句:

1
2
select * from orders;
select * from user where id=查询出的订单的uid;

创建Order和User实体类同上xml方式,这里不再赘述

注解配置

使用注解配置OrderMapper和UserMapper接口类:

测试结果

1
2
3
4
5
6
7
@Test
public void testOneToOne(){
    List<Order> all = mapper.findAll();
    for (Order order : all) {
        System.out.println(order);
    }
}

一对多查询

查询一个用户,与此同时查询出该用户具有的订单

一对多查询的语句

对应的sql语句:

1
2
select * from user;
select * from orders where uid=查询出的用户的id;

修改User实体类同上xml方式,这里不再赘述

注解配置

使用注解配置UserMapper和OrderMapper接口类:

测试结果

1
2
3
4
5
6
7
@Test
public void testOneToMany(){
    List<User> userAndOrderAll = mapper.findUserAndOrderAll();
    for (User user : userAndOrderAll) {
        System.out.println(user);
    }
}

多对多查询

查询用户同时查询出该用户的所有角色

多对多查询的语句

对应的sql语句:

1
2
select * from user;
SELECT * from user_role ,role where user_role.role_id=role.id and user_role.user_id=用户的id

创建Role实体类,修改User实体类同上xml方式,这里不再赘述

注解配置

使用注解配置UserMapper和RoleMapper接口类:

测试结果

1
2
3
4
5
6
7
@Test
public void testManyToMany(){
    List<User> userAndRoleAll = mapper.findUserAndRoleAll();
    for (User user : userAndRoleAll) {
        System.out.println(user);
    }
}

Built with Hugo
Theme Stack designed by Jimmy