Mybatis简介入门
标签:Mybatis

简介入门

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

1. Mybatis 架构

1、 mybatis配置

SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、 MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是MappedStatement的id。

6、 MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、 MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

2. 入门程序

步骤:

  1. 创建SqlSessionFactoryBuilder对象

  2. 加载SqlMapConfig.xml配置文件

  3. 创建SqlSessionFactory对象

  4. 创建SqlSession对象

  5. 执行SqlSession对象执行查询,获取结果User

  6. 打印结果

  7. 释放资源

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>
    <!-- 和spring整合后 environments配置将废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="User.xml"></mapper>
    </mappers>
</configuration>

2.1 查询操作

User.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="user">
    <!--通过id查询一个用户-->
    <select id="findUserById" parameterType="Integer" resultType="com.liuyao.pojo.User">
        SELECT * FROM user WHERE id = #{v}
    </select>
    <!--根据用户名模糊查询用户列表-->
    <select id="findUserByName" parameterType="String" resultType="com.liuyao.pojo.User">
--         SELECT * FROM user WHERE username LIKE '%${value}%'
        SELECT * FROM user WHERE username LIKE "%"#{value}"%"
    </select>
</mapper>

注意上面的 #{v} (占位符)和 ${value} (字符串拼接),模糊查询里面,上面两种方式是等价的。

#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换。#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${} 可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

UserTest

  @Test
    public void test() throws IOException {
//        加载核心配置文件
        String resource = "sqlMapConfig.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

        SqlSession sqlSession = sqlSessionFactory.openSession();

//        User user = sqlSession.selectOne("user.findUserById", 1);
//        System.out.println(user);

        List<User> users = sqlSession.selectList("user.findUserByName", "五");
        for (User u :
                users) {
            System.out.println(u);
        }

2.2 插入操作

User.xml

<!--插入用户-->
<insert id="insertUser" parameterType="com.liuyao.pojo.User">
    INSERT INTO user(username,birthday,address,sex) VALUES (#{username},#{birthday},#{address},#{sex})
</insert>

Test

User user = new User();
user.setUsername("shushu");
user.setBirthday(new Date());
user.setAddress("DaLian");
user.setSex("男");
int i = sqlSession.insert("user.insertUser", user);
System.out.println(i);
// 提交事务
sqlSession.commit();

返回最近插入的元素的id

<insert id="insertUser" parameterType="com.liuyao.pojo.User">
        <selectKey keyProperty="id" resultType="Integer" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO user(username,birthday,address,sex) VALUES (#{username},#{birthday},#{address},#{sex})
    </insert>

2.3 更新操作

User.xml

 <!--更新用户-->
    <update id="updateUserById" parameterType="com.liuyao.pojo.User">
        UPDATE user SET username = #{username},sex=#{sex},birthday=#{birthday},address=#{address} WHERE id= #{id}
    </update>

Test

 User user = new User();
 user.setUsername("liuyao12");
 user.setBirthday(new Date());
 user.setAddress("DaLian");
 user.setSex("男");
 user.setId(32);
 int i = sqlSession.update("user.updateUserById", user);
 sqlSession.commit();
 System.out.println(i);

2.4 删除操作

User.xml

<!--删除用户-->
<delete id="deleteUserById" parameterType="Integer">
DELETE FROM user WHERE id = #{id}
</delete>

Test

sqlSession.delete("user.deleteUserById", 32);
sqlSession.commit();

3. Mybatis解决jdbc编程的问题

1、 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。

2、 Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3、 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4、 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

4. mybatis与hibernate不同

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

5. Mapper动态代理开发

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

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

  1. Mapper.xml文件中的namespace与mapper接口的类路径相同。

  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同

  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

selectOne和selectList

动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。

namespace

mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。

6. SqlMapConfig.xml配置文件

SqlMapConfig.xml中配置的内容和顺序如下:

properties(属性)

settings(全局配置参数)

typeAliases(类型别名)

typeHandlers(类型处理器)

objectFactory(对象工厂)

plugins(插件)

environments(环境集合属性对象)

environment(环境子属性对象)

transactionManager(事务管理)

dataSource(数据源)

mappers(映射器)

6.1 properties

properties可以用于读取properties文件,如存放数据库配置信息的文件。

<properties resource="db.properties"></properties>
 <property name="driver" value="${jdbc.driver}"/>
...

6.2 typeAlias

typeAlias 用于给pojo起别名:

SqlMapConfig.xml

<typeAliases>
    <!--单个的起别名-->
        <typeAlias type="com.liuyao.pojo.User" alias="User"></typeAlias>
    <!--针对一个包-->
      <package name="com.liuyao.pojo"></package>
</typeAliases>

User.xml

<!--通过id查询一个用户-->
<select id="findUserById" parameterType="Integer" resultType="User">
        SELECT * FROM user WHERE id = #{v}
</select>

mybatis支持别名:

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map

6.3 mappers(映射器)

Mapper配置的几种方法:

  1. <mapper resource=" " />

使用相对于类路径的资源(现在的使用方式)

如: <mapper resource="sqlmap/User.xml" />

  1. <mapper class=" " />

使用mapper接口类路径

如: <mapper class="com.liuyao.mybatis.mapper.UserMapper"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

  1. <package name=""/>

注册指定包下的所有mapper接口

如: <package name="com.liuyao.mybatis.mapper"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

7. 输入映射与输出映射

7.1 parameterType(输入类型)

<select id="findUserById" parameterType="Integer" resultType="User">
SELECT * FROM user WHERE id = #{v}
</select>
<!--插入用户-->
<insert id="insertUser" parameterType="com.liuyao.pojo.User">
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username,birthday,address,sex) VALUES (#{username},#{birthday},#{address},#{sex})
</insert>

7.2 resultType(输出类型)

7.3 resultMap

resultType可以指定将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

如当数据库中的字段名和类中的字段名不相同的时候,可以使用resultMap进行映射

 <resultMap id="newOrders" type="Orders">
        <result column="user_id" property="userId"></result>
</resultMap>
<select id="selectOrdersList" resultMap="newOrders">
        SELECT id, user_id, number, createtime, note FROM orders
 </select>

主要上面的select中,返回值类型由原来的resultType已经换成了resultMap类型。

8. 动态Sql

8.1 if

<select id="selectUserBySexAndName" parameterType="User" resultType="User">
        SELECT * FROM user WHERE 1=1
        <if test="sex !=null and sex != ''">
            AND sex = #{sex}
        </if>
        <if test="username != null and username != ''">
            and username like '%${username}%'
        </if>
</select>

注意字符串类型的数据需要要做不等于空字符串校验。

8.2 where

where可以去掉组合字符串中第一个and,相当于默认添加了一个1=1

  <where>
      <if test="sex !=null and sex != ''">
      	AND sex = #{sex}
      </if>
      <if test="username != null and username != ''">
     	 and username like '%${username}%'
      </if>
</where>

8.3 sql

<sql id="selectall">
        SELECT * FROM user
</sql>
<select id="selectUserBySexAndName" parameterType="User" resultType="User">
        <include refid="selectall"></include>
        <where>
            <if test="sex !=null and sex != ''">
                AND sex = #{sex}
            </if>
            <if test="username != null and username != ''">
                and username like '%${username}%'
            </if>
        </where>
</select>

可以把公用的sql语句提出来作为片段,然后在要用的地方用include引入就可以了。

8.4 foreach

8.4.1 利用QueryVo

UserMapper.java

public List<User> selectUserByIds(QueryVo queryVo);

此时出传入的是包装类QueryVo.java

  private List<Integer> ids;

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

UserMapper.xml

<select id="selectUserByIds" parameterType="QueryVo" resultType="User">
        <include refid="selectall"></include>
        <where>
            <foreach collection="ids" item="i" separator="," open=" id in (" close=")">
                #{i}
            </foreach>
        </where>
 </select>

测试代码:

8.4.2 利用array

UserMapper.java

public List<User> selectUserByIds2(Integer[] ids);

这次传入的是一个数组,在相应的UserMapper.xml中:

<select id="selectUserByIds2" parameterType="QueryVo" resultType="User">
        <include refid="selectall"></include>
        <where>
            <foreach collection="array" item="i" separator="," open=" id in (" close=")">
                #{i}
            </foreach>
        </where>

</select>

注意此时的collection填入的是array,而不是ids,测试结果:

8.4.3 利用list

UserMapper.java

 public List<User> selectUserByIds3(List<Integer> ids);

UserMapper.xml

 <select id="selectUserByIds3" parameterType="QueryVo" resultType="User">
        <include refid="selectall"></include>
        <where>
            <foreach collection="list" item="i" separator="," open=" id in (" close=")">
                #{i}
            </foreach>
        </where>

    </select>

注意上面的collection填入的是list

9. 关联查询

9.1 一对一关联

查询所有的订单,订单关联着用户。

在Orders的pojo文件中加入User属性,并添加set和get方法。

修改OrderMapper.xml文件

<resultMap id="newOrder" type="Orders">
        <id property="id" column="id"></id>
        <result property="userId" column="user_id"></result>
        <result property="number" column="number"></result>
        <result property="createtime" column="createtime"></result>
        <result property="note" column="note"></result>
        
        <!-- association :配置一对一属性 -->
        <!-- property:order里面的User属性名 -->
        <!-- javaType:属性类型 -->
        <association property="user" javaType="User">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
        </association>
    </resultMap>

    <select id="selectOrderandUser" resultMap="newOrder">
      SELECT
      o.id,
      o.user_id,
      o.number,
      o.createtime,
      o.note,
      u.username,
      u.address
      FROM orders o LEFT JOIN user u ON
      o.user_id=u.id
    </select>

测试结果:

9.3 一对多关联

OrderMapper.xml

<resultMap id="newUser" type="User">
        <id column="user_id" property="id"></id>
        <result column="username" property="username"></result>
        <collection property="ordersList" ofType="Orders">
            <id column="id" property="id"></id>
            <result column="number" property="number"></result>
        </collection>
    </resultMap>
    <select id="selectUserList" resultMap="newUser">
         SELECT
      o.id,
      o.user_id,
      o.number,
      o.createtime,
      o.note,
      u.username,
      u.address
      FROM  user u LEFT JOIN orders o ON
      o.user_id=u.id
    </select>
  • 17 min read

CONTRIBUTORS


  • 17 min read