概述

mybatis 是一个用java编写的持久层框架, 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程,它使用了ORM思想实现了结果 集的封装

ORM Object Relational Mapping 对象关系映射,把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表

入门案例

  1. 创建数据库,创建User表

  2. 创建maven工程并导入坐标

    <project ...>
    ...
    <packaging>jar</packaging> <dependencies>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
    </dependency>
    </dependencies>
    </project>
  3. 编写实体类

    public class User{
    private Integer id;
    private String username;
    private String password;
    getter()...
    setter()...
    toString()...
    }
  4. 创建UserDao接口

    public interface UserDao{
    List<User> findAll(); //查询所有用户
    }
  5. 创建主配置文件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">
    <!-- mybatis的主配置文件 -->
    <configuration>
    <!-- 配置环境 -->
    <environments default="mysql">
    <!-- 配置mysql的环境 -->
    <environment id="mysql">
    <!-- 配置事务的类型 -->
    <transactionManager type="JDBC"></transactionManager>
    <!-- 配置数据源(连接池) -->
    <dataSource type="POOLED">
    <!-- 配置连接数据库的四个基本信息 -->
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </dataSource>
    </environment>
    </environments> <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
    <mappers>
    <mapper resource="com/whteway/dao/UserDao.xml"/>
    </mappers>
    </configuration>
  6. 创建映射配置文件UserDao.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="com.whteway.dao.UserDao">
    <!-- 配置查询所有,id为对应方法名,resultType指定结果封装位置 -->
    <select id="findAll" resultType="com.whteway.domain.User">
    select * from user
    </select>
    </mapper>
  • 注意

    • Mybatis中把持久层的操作接口名称和映射文件也叫做Mapper,所以dao也可以叫mapper
    • 在idea中创建com.whteway.dao包时是三级结构,创建目录时是一级目录
    • 映射配置文件的位置结构必须和dao接口的包结构相同
    • 映射配置文件的mapper标签的namespace属性的取值必须是dao接口的全限定类名
    • 映射配置文件的操作标签的id属性必须是对应的方法名
    • 遵循以上规则,则不用写dao实现类
  1. 使用

    public void test(){
    //1.读取配置文件
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.创建SqlSessionFactory工厂
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);
    //3.使用工厂生产SqlSession对象
    SqlSession session = factory.openSession();
    //4.使用SqlSession创建Dao接口的代理对象
    UserDao userDao = session.getMapper(UserDao.class);
    //5.使用代理对象执行方法
    List<User> users = userDao.findAll();
    for(User user: users)
    System.out.println(user);
    //6.释放资源
    session.close();
    in.close();
    }
  • 注解方式

    • 主配置文件SqlMapConfig.xml

      ...
      <mappers>
      <!-- <mapper resource="com/whteway/dao/UserDao.xml"/> -->
      <mapper class="com.whteway/dao/UserDao"/>
      </mappers>
      ...
    • UserDao.java

      public interface UserDao{
      @Select("select * from uesr")
      List<User> findAll();
      }
    • 删除UserDao.xml

  • 连接池

    mybatis连接池提供三种方式的配置

    • 配置位置:主配置文件中dataSource标签的type属性

    • type取值:

      • POOLED

        采用传统的javax.sql.DataSource规范中的连接池

      • UNPOOLED

        采用传统的获取连接的方式,实现了DataSource接口,没有用连接池思想

      • JNDI

        采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器能拿到的DataSource不一样,如果不是web或者maven的war工程,就不能使用

  • 事务管理

    • 通过SqlSession的 commit() 和 rollback() 方法
    • SqlSession openSession(boolean autoCommit); 传入false开启事务
  • 自定义dao实现类(不常用, 用来研究原理)

    public class UserDaoImpl implements UserDao{
    private SqlSessionFactory factory;
    public UserDaoImpl(SqlSessionFactory factory){
    this.factory = factory;
    }
    public List<User> findAll(){
    //使用工厂创建SqlSession对象
    SqlSession session = factory.openSession();
    //使用session对象执行sql语句
    List<User> users = session.selectList("映射配置文件的namespace.findAll");
    session.close();
    //返回查询结果
    return users;
    }
    }
    //-----------------使用----------------
    public void test(){
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in); UserDao userdao = new UserDaoImpl(factory);
    List<User> users = userDao.findAll(); in.close();
    }

流程分析

使用过程

  1. 读配置文件

    读取配置文件时用绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法

    • 使用类加载器,它只能读取类路径的配置文件
    • 使用SerbletContext对象的getRealPath()
  2. 创建SqlSessionFactory工厂

    建造者模式(Builder Pattern)

  3. 使用工厂生产SqlSession对象

    工厂模式(Factory Pattern)

  4. 使用SqlSession创建Dao接口的代理对象

    代理模式(Proxy Pattern)

  5. 使用代理对象执行方法

  6. 释放资源

自定义dao中selectList()方法的执行流程,也是代理对象增强的逻辑

  1. 注册驱动,获取Connection对象(需要数据库信息)

    • 通过SqlMapConfig.xml的数据库信息,解析xml文件用到的是dom4j技术
  2. 获取预处理对象PreparedStatement(需要sql语句)
    • 通过SqlMapConfig.xml中的mapper标签定位UserDao.xml,映射配置文件中有sql语句
  3. 执行查询,得到结果集ResultSet
  4. 遍历结果集用于封装
    • 根据UserDao.xml中的resultType反射获得User对象,User对象的属性名和表中列名一致,可以一一封装进user对象中,再把user对象封装进list中
  • 所以,要想让selectList()执行,需要提供两个信息,连接信息和映射信息,映射信息包括sql语句和封装结果的实体类的全限定类名,所以映射信息可以用map存储

创建代理对象流程

  • 根据dao接口的字节码创建dao的代理对象

    public <T> T getMapper(Class<T> daoInterfaceClass){
    /*
    类加载器,使用和被代理对象相同的类加载器
    接口数组,和被代理对象实现相同的接口
    增强逻辑,自己提供,此处是一个InvocationHandler接口,需要写一个该接口的实现类,在其中调用selectList()方法
    */
    Proxy.newProxyInstance(类加载器, 接口数组,增强逻辑);
    }

单表CRUD

DML操作后要调用SqlSession.commit()方法进行事务提交

需要指定参数类型(实体类的全限定类名,基本类型可以用int,Integer,java.lang.Integer)

SQL语句传参时需要用#{实体类的属性值},此处的属性值是setter方法的set后的字符串并首字母小写

  • 保存 UserDao.saveUser(User user);

    • xml

      <insert id="saveUser" parameterType="com.whteway.domain.User">
      insert into user(username, address, sex, birthday) values(#{id},#{username},#{password})
      </insert>
  • 更新 UserDao.updateUser(User user);

    • xml

      <update id="updateUser" parameterType="com.whteway.domain.User">
      update user set username=#{username}, password#{password}) where id=#{id}
      </insert>
    • 注解

  • 删除 UserDao.deleteUser(Integer userId);

    • xml

      <delete id="deleteUser" parameterType="Integer">
      update from user where id=#{uid}
      </delete>
  • 查询单个 UserDao.findById(Integer id);

    • xml

      <select id="findById" parameterType="INT" resultType="com.whteway.domain.User">
      select * from user where id=#{id}
      </select>
  • 查询多个 UserDao.findByName(String name);

    • xml,传参的时候需要加%%(推荐,底层用的是PerparedStatement执行语句),不加%%可以使用固定写法 '%${value}%'(底层用的是Statement执行语句)

      <select id="findByName" parameterType="string" resultType="com.whteway.domain.User">
      select * from user where username like #{name}
      /* select * from user where username like '%${value}' */
      </select>
  • 聚合函数 UserDao.findTotal();

    • xml

      <select id="findTotal" resultType="int">
      select count(id) from user;
      </select>

扩展

  • OGNL表达式

    • Object Graphic Navigation Language 对象 图 导航 语言

    • 通过对象的取值方法来获取数据,省略get

      如:user.getUsername(); --> user.username

    • mybatis中可以直接写username而不写user.,因为在parameterType中已经指定了类

  • 根据自定义的条件类查询 UserDao.findByName(QueryVo vo); 设QueryVo中有属性User

    • xml

      <select id="findByName" parameterType="com.whteway.domain.QueryVo" resultType="com.whteway.domain.User">
      select * from user where username like #{user.username}
      </select>
  • 获取保存数据的id

    • sql中有一条语句"select last_insert_id();"可以返回上一条保存的记录的id,使用下面的配置后mybatis会将查出来的id值存入作为参数的user对象中

      <insert ...>
      <selectKey keyProperty="id" keyColum="id" resultType="int" order="AFTER">
      select last_insertid()
      </selectKey>
      insert ...
      </insert>
  • 返回值与列名的对应

    • 返回值可以是基本类型,也可以是pojo(Plain Ordinary Java Object,普通java对象,即javaBeans)

    • 当实体类属性和查询结果列名不一致时会导致数据无法封装进resultType中

      • 可以在sql语句中通过起别名的方式解决

      • 在映射配置文件中配置查询结果的列名和实体类属性名的对应关系,设User中有uid,name,pwd属性

        <!-- 定义 -->
        <resultMap id="userMap" type="com.whteway.domain.User">
        <id property="uid" column="id"></id>
        <result property="name" column="username"></result>
        <result property="pwd" column="password"></result>
        </resultMap> <!-- 使用,替换resultType -->
        <select id="findAll" resultMap="userMap">
        select * from user
        </select>
  • 自定义dao实现类完成CRUD

    • 用session的不同方法执行sql语句,传入映射配置文件的namespace.SQL语句的id和sql需要的参数

      selectOne,selectList,insert,update

    public class UserDaoImpl implements UserDao{
    //只有第二步需要变化
    @Override
    public void saveUser(User user){
    //1.使用工厂创建SqlSession对象
    SqlSession session = factory.openSession();
    //2.使用session对象执行sql语句
    session.insert("com.whteway.dao.UserDao.saveUser", user);
    //3.释放资源
    session.close();
    //4.返回查询结果
    return;
    }
    }
  • properties标签

    配置properties,可以用标签配,也可以引用外部配置文件信息

    resource属性,用于指定配置文件的位置,按照类路径的写法来写,并且必须存在于类路径下(放在和SqlMapConfig.xml相同路径下)

    url属性,按照url的写法来写地址:协议://主机:端口 URI

    <configuration>
    <properties resource="jdbcConfig.properties">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </properties>
    <!-- 在实际配置中用"${driver}"取值 -->
    </configuration>
  • typeAliases标签

    配置别名,只能配置实体类的别名,当指定了别名就不再区分大小写

    mybatis为基本类型和String配置了别名

    <typeAliases>
    <typeAlias type="com.whteway.domain.User" alias="user"></typeAlias>
    </typeAliases>
  • package标签

    在typeAliases中使用:为整个包中的类配置别名,当指定之后,该包下的实体类都会以类名注册别名

    在mappers中使用:指定dao接口所在的包,指定后,不需要再写resource或class了

    <typeAliases>
    <package name="com.whteway.domain"></package>
    </typeAliases>
    <mappers>
    <package name="com.whteway.dao"></package>
    </mappers>

动态SQL语句

  • if标签:满足条件则将标签内的语句拼接到sql语句后

    <!-- 根据条件查询,传入的条件可能是id,也可能是username -->
    <select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from where 1=1
    <if test="username != null">and username=#{username}</if>
    <if test="id != null">and id=#{id}</if>
    </select>
  • where标签:sql后加 where 再将标签内的语句拼接到sql后

    <!-- if标签代码改进 -->
    <select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from
    <where>
    <if test="username != null">and username=#{username}</if>
    <if test="id != null">and id=#{id}</if>
    </where>
    </select>
  • foreach标签

    循环将标签内部的语句和属性中的片段拼接到sql后

    open属性:开始片段;close属性:结束片段;separator属性:每个元素的分割符

    <!-- 根据queryvo中提供的id集合,查询用户信息 -->
    <select id="findByIds" resultMap="userMap" parameterType="queryvo">
    select * from user
    <where>
    <if test="ids != null and ids.size() > 0">
    <foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
    #{id}
    </foreach>
    </if>
    </where>
    </select>
  • sql标签

    抽取重复的sql语句

    <!-- 定义,末尾不要加分号 -->
    <sql id="defaultUser">select * from user</sql>
    <!-- 使用 -->
    <select id="findUserByCondition" resultMap="userMap" parameterType="user">
    <include refid="defaultUser"></include>
    where id=#{id}
    </select>

多表查询

关键在于SQL语句的编写,在实体类和映射配置中只能定义查询结果的封装

定义映射关系时,column属性的值是对应查询结果的列名的,如果有重复字段名,则需要在sql语句中使用别名,那么在resultMap中的column属性值也要与别名对应

  • 一对多(Mybatis中把多对一当作一对一处理)

    user(id, username, password),account(id, uid, money);

    User.java

    public class User implements Serili{
    /* ... */
    //一对多关系映射,主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    /* getters and setters */
    }
    <resultMap id="userAccountMap" type="user">
    <!-- 普通属性的关系映射... -->
    <!-- 一对多的关系映射,查询时user.account.id需要取别名 -->
    <collection property="accounts" ofType="account">
    <id column="aid" property="id"></id>
    <result column="uid" property="uid"></result>
    <result column="money" property="money"></result>
    </collection>
    </resultMap>

    Acount.java

    public class Account implements Serializable{
    /* ... */
    //一对一关系映射,从表实体应该包含主表实体的对象引用
    private User user;
    /* getters and setters */
    }
    <resultMap id="accountUserMap" type="account">
    <!-- 普通属性的关系映射... -->
    <!-- 一对一的关系映射 -->
    <association property="user" column="uid" javaType="user">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="password" property="password"></result>
    </association>
    </resultMap>
  • 多对多,需要使用中间表

    user(id, username, password),role(id, name),user_role(uid, rid);

    在User类中加Role的集合roles,并在UserDao.xml中配置resultMap,使用collection标签配置roles

    在Role类中加User的集合users,并在RoleDao.xml中配置resultMap,使用collection标签配置users

延迟加载

  • 问题:一个用户有多个账户,在查询用户时,即使用了多表查询的sql语句,也可能用不到关联的帐户信息,这时候查询账户表浪费资源

  • 延迟加载(按需加载,懒加载)

    在真正使用数据时才发起查询,不用的时候不查询

    比如:查询用户和账户时,输入的语句是,实际运行时,由于调用查询方法一定会有User对象或列表接收返回值,所以会立即"select * from user",当用到user.accounts时,才会执行"select * from account where uid=?"

  • 立即加载

    只要调用方法,马上发起查询

  • 方式选择

    一对多、多对多:通常采用延迟加载,即有查询结果带集合时延迟

    多对一、一对一:通常采用立即加载

  • 配置开启延迟加载

    <!-- SqlMapConfig.xml -->
    <configuration>
    <settings>
    <!-- 打开支持延迟加载开关 -->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 将积极(立即)加载改为消息(延迟)加载,3.4.2及之后默认为false,可以不配 -->
    <Setting name="aggressiveLazyLoading" value="false" />
    </settings>
    </configuration>

缓存

  • 缓存:存在于内存中的数据

    缓存可以减少和数据库的交互次数,提高执行效率

  • 适用情况

    适用:经常查询且不常改变的,数据的正确性对最终结果影响不大的

    不适用:经常改变的,正确性对最终结果影响很大的,如汇率,股价等

  • Mybatis一级缓存

    • 指SqlSession对象的缓存,当SqlSession对象消失时,缓存随之消失
    • 当执行查询的结果会同时存入到SqlSession提供的一块区域中,该区域结构是一个Map,当查询时,mybatis会先去sqlsession中查询是否有对应数据,如果有则直接用
    • Mybatis默认开启一级缓存
    • 缓存Map的value是对象,所以两次查询得到的对象是同一个
    • sqlSession.clearCache(); //不销毁sqlSession清空缓存
    • 当执行DML时,会清空一级缓存
  • Mybatis二级缓存

    • 指SqlSessionFactory对象的缓存

    • 配置开启二级缓存(三步)

      <!-- SqlMapConfig.xml -->
      <configuration>
      <settings>
      <setting name="cacheEnabled" value="true" />
      </settings>
      </configuration>
      <!-- UserDao.xml -->
      <mapper ...>
      <cache/>
      <select ... useCache="true"> ... </select>
      </mapper>
    • 二级缓存中存入的是数据,而不是对象,所以两次查询得到的对象不是同一个

注解开发

  • SqlMapConfig.xml 主配置文件不能省略

    注意:只要使用注解开发,如果同时存在映射配置文件,即使mappper中指定了class属性,也会报错

    <configuration>
    <!-- ... -->
    <!-- 指定带有注解的dao接口位置 -->
    <mappers>
    <package name="com.whteway.dao"></package>
    </mappers>
    </configuration>
  • CRUD注解,加在dao接口的方法上

    • @Select("sql") 相当于select标签
    • @Insert("sql") 相当于insert标签
    • @Update("sql") 相当于update标签
    • @Delete("sql") 相当于delete标签
  • 映射关系注解

    • @Results 相当于resultMap标签

    • @Result 相当于result标签

    • @ResultMap 相当于CRUD标签的resultMap属性

    • 使用方式,@Results可以直接用在方法上(不用指定id属性)

      指定id属性后,其他方法可以用@ResultMap("resultsId")调用对应@Results

      @Results(value={
      @Result(id="true", column="id", property="userId"),
      @Result(column="username", property="name"),
      @Result(column="password", property="psw")
      })
  • 多表查询的注解

    • @One 指定一的一方,用在Result标签中

      select属性指向一个方法,该方法可以通过User主键查询User,该方法必须存在

      fetchType属性指定延迟加载策略,FetchType.DEFAULT(),FetchType.LAZY(),FetchType.EAGER(),

      @Select("select * from account")
      @Results(value={
      /* ... */
      @Result(column="uid", property="user", one=@One(select="com.whteway.UserDao.findById", fetchType=FetchType.EAGER))
      })
      List<Account> findAll();
    • @Many 指定多的一方,用在Result标签中

      select属性指向一个方法,该方法可以通过User主键(Account的外键)查询所有符合条件的Account,该方法必须存在

      fetchType同@One

      @Select("select * from user")
      @Results(value={
      /* ... */
      @Result(column="id", property="accounts", many=@Many(select="com.whteway.AccountDao.findByUid", fetchType=FetchType.DEFAULT))
      })
      List<User> findAll();
  • 注解开启二级缓存

    @CacheNamespace(blocking = true) 用在Dao接口上

最新文章

  1. eclipse gradle插件(buildship)的安装和使用
  2. JS,html压缩及混淆工具
  3. XCL-Charts图表库简要教程及常见问题
  4. IQ推理:红眼睛和蓝眼睛
  5. javascript基础知识复习一
  6. 项目如何脱离TFS 2010的管理
  7. 申请iOS开发者证书
  8. git强制覆盖本地文件
  9. saltstack实战4--综合练习2
  10. 启动列表的activity
  11. [转贴]JAVA :CXF 简介
  12. iOS中你必须了解的多线程
  13. OC——NSString的常用方法
  14. Ubuntu在下面LAMP(Linux+Apache+MySQL+PHP) 开发环境的搭建
  15. 关于Cocos2dx之JS创建项目
  16. Docker - 手动迁移镜像
  17. LINUX 笔记-crontab命令
  18. /etc/fstab文件分析(第二版)
  19. SVN分支与合并【超详细的图文教程】(转载)
  20. Dicom文件转mhd,raw文件格式

热门文章

  1. JDK1.8 LocalDate 使用方式;LocalDate 封装Util,LocalDate工具类(一)
  2. Elasticsearch 报错:Fielddata is disabled on text fields by default. Set `fielddata=true` on [`your_field_name`] in order to load fielddata in memory by uninverting the inverted index.
  3. 1.Java介绍
  4. JQuery$.extend()用法
  5. 【Linux】查看端口和进程
  6. Spring Cloud微服务安全实战_3-2_第一个API及注入攻击防护
  7. Python面向对象 | 抽象类和接口类
  8. java 构造方法+this+super
  9. ESP8266 SDK开发: 测试下诱人的程序
  10. 踩iviewui中Tabs 标签页数据绑定坑