MyBatis学习笔记(一)

377 阅读12分钟

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对象并返回。

使用JDBC存在的问题

JDBC的编程步骤

  1. 加载数据库驱动
  2. 创建获取数据库连接
  3. 创建Statement对象
  4. 执行SQL语句并返回ResultSet结果集
  5. 处理结果集
  6. 释放资源

存在的问题

  1. 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。如果使用数据库连接池可解决此问题。
  2. SQL语句在代码中硬编码,造成代码不易维护,实际应用中SQL变化的可能较大,SQL语句变动需要改变 java代码。
  3. 使用preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

MyBatis架构

MyBatis架构图

图1 MyBatis架构图

架构图解释

MyBatis配置文件

  • SqlMapConfig.xml

    MyBatis的全局配置文件,配置了MyBatis的运行环境等信息

  • SqlSessionFactory

    会话工厂,用于创建SqlSession,通过MyBatis环境等配置信息构造SqlSessionFactory

  • SqlSession

    会话,操作数据库主要通过SqlSession进行

  • Executor

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

  • MappedStatement

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

    • 输入映射

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

    • 输出映射

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

Mybatis入门程序

MyBatis下载

MyBatis下载地址

点击上述链接进入页面后,再点击下图中的位置即可下载;下载完毕后压缩包中有MyBatis的依赖包(在lib目录下)。

图2

环境配置

  1. 导入MyBatis依赖包以及数据库连接驱动

  2. 编写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"></transactionManager>
           <!--数据库连接池-->
           <dataSource type="POOLED">
               <!--配置数据库连接信息-->
               <property name="driver" value="com.mysql.jdbc.Driver"></property>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatistest?characterEncoding=UTF-8"></property>
               <property name="username" value="数据库用户名"></property>
               <property name="password" value="数据库密码"></property>
           </dataSource>
       </environment>
    </environments>
    </configuration>
    
  3. 编写日志输出文件log4j.properties,日志输出文件的编写如下:

    # Global logging configuration
    log4j.rootLogger=DEBUG, stdout
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
  4. 在数据库中创建表,我创建的是user表:

    CREATE TABLE `user` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(32) NOT NULL COMMENT '用户名称',
    `birthday` date DEFAULT NULL COMMENT '生日',
    `sex` char(1) DEFAULT NULL COMMENT '性别',
    `address` varchar(256) DEFAULT NULL COMMENT '地址',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
    
  5. 编写数据库中表的对应的pojo(Plain Ordinary Java Object,其实就是Java Bean)

  6. 编写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">
    <!--
    namespace:命名空间,用于隔离SQL
    ->
    <mapper namespace="com.xurenyi.pojo.User">
    <!--
       id:sql的id
       parameterType:输入参数的类型
       resultType:输出结果的类型,应该填写对应POJO的全路径
       #{v}:占位符,相当于jdbc中的?
    -->
    <select id="findUserById" parameterType="Integer"   resultType="com.xurenyi.pojo.User">
       select * from user where id=#{v}
    </select>
    </mapper>
    
  7. 在SqlMapperConfig.xml中配置UserMapper文件,写法如下:

    <!--配置UserMapper.xml文件的位置-->
    <mappers>
       <mapper resource="config/UserMapper.xml"></mapper>
    </mappers>
    

案例

根据id查询单个用户

  1. 在UserMapper.xml中编写SQL语句:
    <!--
    mapper:用于编写SQL语句,mapper元素中可以写多个SQL语句
    namespace:命名空间,用于隔离SQL
    -->
    <mapper namespace="com.xurenyi.pojo.User">
    <!--
       id:SQL语句的id
       parameterType:输入参数的类型
       resultType:输出结果的类型,应该填写对应POJO的全路径
       '#{v}':占位符,相当于jdbc中的?
    -->
    <!--
       根据id查询单个用户
    -->
    <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User">
       select * from user where id=${value}
    </select>
    </mapper>
    
  2. 编写测试方法:
    //根据id查找单个用户
    @Test
    public void findUserById() throws IOException {
       //加载SqlMapConfig.xml配置文件,把配置文件的路径作为参数传入
       InputStream in =  Resources.getResourceAsStream("config/SqlMapConfig.xml");
       //创建SqlSessionFactoryBuilder
       SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
       //获取SqlSessionFactory(根据配置文件创建SqlSessionFactory)
       SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in);
       //获取SqlSession
       SqlSession sqlSession = sqlSessionFactory.openSession();
       //执行select操作,传入参数是SQL语句的id、SQL语句中的查找参数
       User user = sqlSession.selectOne("findUserById", 1);
       System.out.println(user);
    }
    

注意: 由于下面的例子中测试代码的前面部分与根据id查询单个用户中的测试代码是一样的,所以在下述的例子中,就只写SQL语句(写在<mapper></mapper>元素中)以及通过sqlSession进行操作数据库的语句(例如根据id查询单个用户测试代码中的User user = sqlSession.selectOne("findUserById", 1);这句),省略获取SqlSession对象的代码。

根据用户名模糊查询用户

  • 方式一
    • SQL语句
      <!--模糊查询-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like #{v}
      </select>
      
    • 测试代码
      /*
      获取SqlSession对象的代码,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "%张%");
      
  • 方式二
    • SQL语句
      <!--模糊查询-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like '%${value}%'
      </select>
      
    • 测试代码
      /*
      获取SqlSession对象的代码,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "张");
      
  • 方式三
    • SQL语句
      <!--模糊查询-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like "%"#{v}"%"
      </select>
      
    • 测试代码
      /*
      获取SqlSession对象的代码,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "张");
      

添加用户

  • SQL语句
    <!--添加用户
    参数类型是User对象,所以在SQL语句中取参数值的时候,需要把User实体类里面的属性名写进去,而不能在'#{}'里面随便写几个字母
    -->
    <insert id="addUser" parameterType="com.xurenyi.pojo.User">
       <!--取参数的时候需要把User里面的属性写进'#{}',而不能随便写-->
       insert into user values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>
    
  • 测试代码
    /*
    获取SqlSession对象的代码,略
    */
    User user = new User();
    user.setId(null);
    user.setUsername("用户名");
    user.setBirthday(new Date());
    user.setSex("男");
    user.setAddress("用户地址");
    // 需要"改变"数据库中数据的操作(例如:插入、删除、修改数据)都有一个int类型的返回值,该返回值表示对数据库中记录影响的行数
    int addUser = sqlSession.insert("addUser", user);
    sqlSession.commit();   //需要自己提交事务(在增删改查这四个操作中,除了查询操作,其余三个操作都需要提交事务)
    

添加用户返回ID

  • 需求:在使用MyBatis插入一个用户数据以后,获取该用户的id。
  • select 1613501;
    • 这是MySQL中提供的查询最新添加数据的id,但是执行该条语句时,需要先执行一条插入语句才能返回id,否则就返回0。
  • SQL语句
    <insert id="addUser" parameterType="com.xurenyi.pojo.User">
       <!--
           标签实现主键返回
           keyColumn:主键对应数据库表中的哪一列
           keyProperty:主键对应pojo中的哪一个属性
           order:设置在执行insert语句之前执行查询id的操作,还是在执行insert语句之后执行查询id的操作
       -->
       
       <selectKey keyColumn="id" keyProperty="id" resultType="Integer" order="AFTER">
           select 1613501
       </selectKey>
       
       <!--取参数的时候需要把User里面的属性写进#{},而不能随便写-->
       insert into user values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>
    
  • 测试代码
    /*
    获取SqlSession对象的代码,略
    */
    User user = new User();
    user.setId(null);
    user.setUsername("用户名");
    user.setBirthday(new Date());
    user.setSex("男");
    user.setAddress("用户地址");
    // 需要"改变"数据库中数据的操作(例如:插入、删除、修改数据)都有一个int类型的返回值,该返回值表示对数据库中记录影响的行数
    int addUser = sqlSession.insert("addUser", user);
    sqlSession.commit(); //需要自己提交事务(在增删改查这四个操作中,除了查询操作,其余三个操作都需要提交事务)
    System.out.println(user.getId()); //在提交事务之后获取id并打印
    

根据id修改用户数据

  • SQL语句
    <!--根据id修改用户数据-->
    <update id="updateUserById" parameterType="com.xurenyi.pojo.User">
       update user set address=#{address},birthday=#{birthday}  where id=#{id}
    </update>
    
  • 测试代码
    /*
    获取SqlSession对象的代码,略
    */
    User user = new User();
    user.setId(28);
    user.setAddress("修改后的地址");
    user.setUsername("修改后的姓名");
    user.setSex("女");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date = simpleDateFormat.parse("1994-01-01 17:06:00");
    user.setBirthday(date);
    sqlSession.update("updateUserById", user);
    //提交事务
    sqlSession.commit();
    //关闭sqlSession
    sqlSession.close();
    

根据id删除用户

  • SQL语句
    <!--根据id删除用户数据-->
    <delete id="deleteUserById" parameterType="Integer">
       delete from user where id=#{id}
    </delete>
    
  • 测试代码
    /*
    获取SqlSession对象的代码,略
    */
    int count=sqlSession.delete("deleteUserById",28);
    //提交事务
    sqlSession.commit();
    System.out.println(count);
    //关闭sqlSession
    sqlSession.close();
    

小结

  • #{}和${}的区别
    • #{}

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

    • ${}

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

  • parameterType和resultType
    • parameterType 指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在 sql 中。
    • resultType 指定输出结果类型,mybatis 将 sql 查询结果的一行记录数据映射为resultType 指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器 List 中。
  • selectOne和selectList
    • selectOne selectOne用于查询一条记录,如果使用selectOne查询多条记录会报错。
    • selectList selectList用于查询一条或多条记录。
  • 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定义输出结果的类型
  • 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需要具有很强的经验和能力才行。
    • 总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

SqlMapConfig.xml配置文件详解

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

图3 SqlMapConfig.xml中配置的内容和顺序
这里选讲几个元素。

  • properties(属性)

    • properties是用来加载外部配置文件的,其写法如下:

      <!--使用resource加载外部配置文件-->
      <properties resource="外部配置文件的路径">
         <!--如果外部配置文件中有该属性,那么内部定义的属性被外部属性覆盖-->
         <property name="" value=""/>
      </properties>
      
    • MyBatis加载属性的顺序

      先读取properties元素体内自定义的属性,再读取properties中resource指定路径的外部配置文件中的属性,如果在外部配置文件中有同名属性,则后者会覆盖前者。

  • typeAliases(类型别名)

    有时候写实体类的全路径比较麻烦,所以可以取一个别名,这样可以在Mapper.xml中直接写别名调用。

    • 定义单个别名

      定义单个别名的配置写法如下:

      <typeAliases>
         <!--定义单个别名
             type:需要被取别名的对象全路径
             alias:对应的别名
         -->
         <typeAlias type="com.xurenyi.pojo.User" alias="user"></typeAlias>
      </typeAliases>
      
    • 批量定义别名

      批量定义别名的配置写法如下:

      <typeAliases>
        <!--批量定义别名
            会扫描整个包下的类,别名是类名(大小写不敏感)
        -->
        <package name="com.xurenyi.pojo"></package>
      
    ```
  • mappers(映射器)

    • 用法1

      <mapper resource="mapper.xml配置文件的全路径"></mapper>

    • 用法2 <mapper class="对应的mapper类的路径"></mapper>

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

    • 用法3 <package name="包路径"></mapper>

      此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。 实际项目开发中,会使用这种方法。