从JDBC谈起

创建maven工程

引入mysql依赖包

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>5.1.32</version>
  5. </dependency>

准备数据

创建数据库

  1. CREATE DATABASE ssmdemo;

创建表

DROP TABLE IF EXISTS tb_user;

CREATE TABLE tb_user (
    id char(32) NOT NULL,
    user_name varchar(32) DEFAULT NULL,
    password varchar(32) DEFAULT NULL,
    name varchar(32) DEFAULT NULL,
    age int(10) DEFAULT NULL,
    sex int(2) DEFAULT NULL,
    birthday date DEFAULT NULL,
    created datetime DEFAULT NULL,
    updated datetime DEFAULT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据

INSERT INTO ssmdemo.tb_user ( userName, password, name, age, sex, birthday, created, updated) VALUES ( ‘zpc’, ‘123456’, ‘测试2’, ‘22’, ‘1’, ‘1990-09-02’, sysdate(), sysdate());
INSERT INTO ssmdemo.tb_user ( userName, password, name, age, sex, birthday, created, updated) VALUES ( ‘hj’, ‘123456’, ‘测试2’, ‘22’, ‘1’, ‘1993-09-05’, sysdate(), sysdate());

JDBC基础代码回顾

缺点分析
image.png

MyBatis 简介

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,是一个基于Java的持久层框架。

  • 持久层: 可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏,在断电或者其他情况下,重新开启系统仍然可以读取到这些数据。
  • 优点: 可以使用巨大的磁盘空间存储相当量的数据,并且很廉价
  • 缺点:慢(相对于内存而言)

为什么使用 MyBatis

在我们传统的 JDBC 中,我们除了需要自己提供 SQL 外,还必须操作 Connection、Statment、ResultSet,不仅如此,为了访问不同的表,不同字段的数据,我们需要些很多雷同模板化的代码,闲的繁琐又枯燥。

而我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上。
[

](http://www.mybatis.org/mybatis-3/getting-started.html)
官方文档:https://mybatis.org/mybatis-3/zh/index.html

Mybaits整体架构

mybatis课件 - 图2

1.配置文件
全局配置文件:mybatis-config.xml作用:配置数据源,引入映射文件
映射文件:XxMapper.xml 作用:配置sql语句、参数、结果集封装类型等
2.SqlSessionFactory作用:获取SqlSession
通过new SqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流

3.SqlSession作用:执行CRUD操作。
4.Executor
执行器,SqlSession通过调用它来完成具体的CRUD它是一个接口,提供了两种实现:缓存的实现、数据库的实现。
5.Mapped Statement
在映射文件里面配置,包含3部分内容:
具体的sql,sql执行所需的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:
HashMap,基本数据类型,pojo
6.xxxMapper.xml映射文件
名称空间必须改成UserMapper接口的全路径,StatementId必须和接口方法名一致,结果集的封装类型已经和方法的返回类型一致
7.mybatisconfig.xml配置文件

快速入门

引入依赖(pom.xml)

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.2.8</version>
</dependency>

全局配置文件(mybatis-config.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>
<properties>
 <property name="driver" value="com.mysql.jdbc.Driver"/>
 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis-110?useUnicode=true&amp;characterEncoding=utf-8&amp;allowMultiQueries=true"/>
 <property name="username" value="root"/>
     <property name="password" value="123456"/>
   </properties>

   <!-- 环境,可以配置多个,default:指定采用哪个环境 -->
   <environments default="test">
      <!-- id:唯一标识 -->
      <environment id="test">
         <!-- 事务管理器,JDBC类型的事务管理器 -->
         <transactionManager type="JDBC" />
         <!-- 数据源,池类型的数据源 -->
         <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis-110" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
         </dataSource>
      </environment>
      <environment id="development">
         <!-- 事务管理器,JDBC类型的事务管理器 -->
         <transactionManager type="JDBC" />
         <!-- 数据源,池类型的数据源 -->
         <dataSource type="POOLED">
            <property name="driver" value="${driver}" /> <!-- 配置了properties,所以可以直接引用 -->
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
         </dataSource>
      </environment>
   </environments>
  </configuration>

配置Map.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="test">

 <!--sql语句 -->
  <select id="findUserById" resultType="com.lingnang.pojo.User" parameterType="String">
        select * from userr where uid = #{id}
  </select>
</mapper>

修改全局配置文件(mybatis-config.xml)

image.png

构建sqlSessionFactory

@Test
    public void test1() throws Exception {
        // 会话工厂
        SqlSessionFactory sqlSessionFactory = null;
        // 配置文件
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 使用SqlSessionFactoryBuilder从xml配置文件中创建SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = null;
        try {
            // 创建数据库会话实例sqlSession
            sqlSession = sqlSessionFactory.openSession();
            // 查询单个记录,根据用户id查询用户信息
            Users user = sqlSession.selectOne("test.findUserById", 3);
            // 输出用户信息
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }


日志分析

mybatis 框架他是日志输出。输出级别是:DEBUG
info debug error warming
info:程序运行时候
debug :调试程序的日志
error : 错误异常
warming : 异常警告

引入日志依赖包

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.5</version>
</dependency>

添加log4j.properties

注意:名称不能改变,必须log4j.properties
必须放在resource目录下面
log4j.properties配置文件信息:

log4j.rootLogger=DEBUG
log4j.logger.org.apache=DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

MyBatis使用步骤总结

1)配置mybatis-config.xml 全局的配置文件 (1、数据源,2、外部的mapper)
2)创建SqlSessionFactory
3)通过SqlSessionFactory创建SqlSession对象
4)通过SqlSession操作数据库 CRUD
5)调用session.commit()提交事务
6)调用session.close()关闭会话

完整的CRUD操作

1.定义dao

image.png

2.实现dao接口

image.png

3.编写xml文件

image.png

4.添加UserDao的测试

image.png

解决数据库字段名和实体类属性名不一致的问题

查询数据的时候,发现查不到userName的信息:

User{id=‘2’, userName=‘null’, password=‘123456’, name=‘静静’, age=22, sex=0, birthday=‘1993-09-05’, created=‘2018-06-30 18:22:28.0’, updated=‘2018-06-30 18:22:28.0’}

原因:数据库的字段名是user_name,POJO中的属性名字是userName两端不一致,造成mybatis无法填充对应的字段信息。 修改方法:在sql语句中使用别名。

解决方案1:在sql语句中使用别名:

image.png

解决方案2: 参考后面的resultMap –mapper具体的配置的时候

resultMap 和 java bean对象建立映射关系

解决方案3:参考驼峰匹配 — mybatis-config.xml 的时候

动态代理Mapper实现类

思考上述CRUD中的问题

1、接口->实现类->mapper.xml
2、实现类中,使用mybatis的方式非常类似
3、xml中的sql statement 硬编码到java代码中。

思考:能否只写接口,不写实现类。只编写接口和Mapper.xml即可?

因为在dao(mapper)的实现类中对sqlsession的使用方式很类似。因此mybatis提供了接口的动态代理。

使用动态代理改造CRUD

// 获取 mapper代理对象
this.userDao = sqlSession.getMapper(UserDao.class);

执行queryUserAll()方法

org.apache.ibatis.binding.BindingException: 
Type interface com.xx.xx.dao.UserDao is not known to the MapperRegistry.

分析原因,在UserMapper.xml中配置接口的全路径
配置userdao,命名空间

<mapper namespace = "com.xxx.xx.dao.UserDao">

完整的例子

1.创建UserMapper接口(对应原UserDao)

image.png

2.创建UserMapper.xml

同上

3.全局配置文件mybatis-config.xml引入UserMapper.xml

image.png

4.创建UserMapper测试用例

image.png

mybatis-config.xml详解

https://mybatis.org/mybatis-3/zh/configuration.html

Mapper XML文件详解

https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

自动生成java bean, dao,mapper文件

https://blog.csdn.net/qq_37910658/article/details/78903354

动态sql

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

1.if

场景:查询男性用户,如果输入了姓名,则按姓名查询

/**
 * 查询男性用户,如果输入了姓名,则按姓名查询
 * @param name
 * @return
 */
List<User> queryUserList(@Param("name") String name);

编写mapper

<select id="queryUserList" resultType="com.xxx.mybatis.pojo.User">
    select * from tb_user WHERE sex=1
    <if test="name!=null and name.trim()!=''">
      and name like '%${name}%'
    </if>
</select>

2.choose、when、otherwise

场景:查询男性用户,如果输入了姓名则按照姓名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找姓名为“鹏程”的用户。

/**
 * 
 * @param name
 * @param age
 * @return
 */
List<User> queryUserListByNameOrAge(@Param("name") String name,@Param("age") Integer age);


编写mapper

<select id="queryUserListByNameOrAge" resultType="com.zpc.mybatis.pojo.User">
    select * from tb_user WHERE sex=1
    <!--
    1.一旦有条件成立的when,后续的when则不会执行
    2.当所有的when都不执行时,才会执行otherwise
    -->
    <choose>
        <when test="name!=null and name.trim()!=''">
            and name like '%${name}%'
        </when>
        <when test="age!=null">
            and age = #{age}
        </when>
        <otherwise>
            and name='鹏程'
        </otherwise>
    </choose>
</select>

3.where 和set

场景一:查询所有用户,如果输入了姓名按照姓名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。

/**
 *
 * @param name
 * @param age
 * @return
 */
List<User> queryUserListByNameAndAge(@Param("name") String name,@Param("age") Integer age);

编写mapper

<select id="queryUserListByNameAndAge" resultType="com.zpc.mybatis.pojo.User">
    select * from tb_user
    <!--如果多出一个and,会自动去除,如果缺少and或者多出多个and则会报错 -->
<where>

<if test="name!=null and name.trim()!=''">
    and name like '%${name}%'
</if>

<if test="age!=null">
    and age = #{age}
</if>

</where>

</select>

场景二:修改用户信息,如果参数user中的某个属性为null,则不修改。

/**
 * 根据id更新用户信息
 *
 * @param user
 */
public void updateUser(User user);

编写mapper

<update id="updateUser">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>


4.foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
比如:按多个Id查询

/**
 * 按多个Id查询
 * @param ids
 * @return
 */
List<User> queryUserListByIds(@Param("ids") String[] ids);

编写mapper

<select id="queryUserListByIds" resultType="com.zpc.mybatis.pojo.User">
    select * from tb_user where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

缓存

Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

一级缓存

在mybatis中,一级缓存默认是开启的,并且一直无法关闭一级缓存满足条件:

1、同一个session中

2、相同的SQL和参数

测试:

@Test
public void testQueryUserById() {
    System.out.println(this.userMapper.queryUserById("1"));

    System.out.println(this.userMapper.queryUserById("1"));
}



执行结果:
image.png

使用:sqlSession.clearCache();可以强制清除缓存

@Test
public void testQueryUserById() {
    System.out.println(this.userMapper.queryUserById("1"));
    sqlSession.clearCache();
    System.out.println(this.userMapper.queryUserById("1"));
}

注意:执行update、insert、delete的时候,会清空缓存

@Test 
public void testQueryUserById() {
    System.out.println(this.userMapper.queryUserById("1"));
    //sqlSession.clearCache();
    User user=new User();
    user.setName("美女");
    user.setId("1");
    userMapper.updateUser(user);
    System.out.println(this.userMapper.queryUserById("1"));
}

二级缓存

mybatis 的二级缓存的作用域是一个mapper的namespace ,同一个namespace中查询sql可以从缓存中命中。

开启二级缓存:

<mapper namespace="com.zpc.mybatis.dao.UserMapper">
    <cache/>
</mapper>

测试:

@Test
public void testCache() {
     System.out.println(this.userMapper.queryUserById("1"));
     sqlSession.close();
        SqlSession sqlSession = sqlSessionFactory.openSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     System.out.println(mapper.queryUserById("1"));
}

开启二级缓存,必须序列化:

image.png

关闭二级缓存:
不开启,或者在全局的mybatis-config.xml 中去关闭二级缓存

<settings>
    <!--开启二级缓存,全局总开关,这里关闭,mapper中开启了也没用-->
    <setting name="cacheEnabled" value="false"/>
</settings>

延迟加载

什么是延迟加载

  1. 延迟加载的条件:resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

  2. 延迟加载的好处:
    需要时再从关联表先从单表查询去关联查询,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。

  3. 延迟加载的实例:如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。

例如:订单:一个订单,对应多个商品。

先查询订单表,在根据订单id 查询订单明细

如何开启延迟加载功能

  1. Mybatis的延迟加载功能默认是关闭的。
  2. 需要在mybatis-config.xml(mybatis全局文件)中通过setting标签配置来开启延迟加载功能。
  3. 开启延迟加载的属性:
    lazyLoadingEnabled:全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。默认为false
    aggressiveLazyLoading:当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。默认为true。

配置

 <settings> 
    <!--开启延迟加载--> 
    <setting name="lazyLoadingEnabled" value="true"/> 

    <!--关闭积极加载--> 
    <setting name="aggressiveLazyLoading" va lue="false"/> 
</settings>

使用示例:

  1. 在resultMap中使用association或者collection,即可使用延迟加载。
  2. 延迟加载需要两个statement语句来完成
  3. 在resultMap中使用association或者collection来配置两个statement直接的管理

resultMap代码 :

 <!--查询订单和创建订单的用户,使用延迟加载--> 
<resultMap id="OrderAndUserLazyLoad" type="Orders"> 
    <id column="id" property="id"/> 
    <result column="user_id" property="userId" />
    <result column="number" property="number" /> 
    <result column="createtime" property="createtime" /> 
    <result column="note" property="note" />

     <!--select:要延迟加载的statement的id colunm:关联两张表的那个列的列名 --> 
    <association property="user" javaType="User" select="findUser" column="user_id"> </association>
</resultMap>

两个statement直接必须存在关联的数据列

<select id="findOrdersByLazyLoad" resultMap="OrderAndUserLazyLoad"> 
    SELECT * FROM orders
</select> 

<select id="findUser" parameterType="int" resultType="User"> 
    SELECT * FROM User WHERE id = #{value} 
</select>
         <br />**mapper代码**
public List findOrdersByLazyLoad();

测试代码

  public void testFindOrdersByLazyLoad() throws Exception{
    SqlSession session = sessionFactory.openSession();
    Mapper mapper = session.getMapper(Mapper.class);
    //只会发送查询订单信息的SQL
    List orders = mapper.findOrdersByLazyLoad();
    for (Orders order : orders){
    //会发生查询用户信息的SQL
     order.getUser();
  }

xml转义符

XML实体中不允许出现”&”,”<”,”>”等特殊字符,否则XML语法检查时将出错,如果编写的XML文件必须包含这些字符,则必须分别写成”&”,”<”,”>”再写入文件中。例如,如果在XML文档中使用类似”<” 的字符, 那么解析器将会出现错误,因为解析器会认为这是一个新元素的开始。


image.png

因为业务,需要在mybatis中,使用到大于号,小于号,所以就在SQL中直接使用了。

SELECT * FROM test WHERE 1 = 1 AND start_date <= CURRENT_DATE
AND end_date >= CURRENT_DATE

可是,在执行时,总报错误:

``Error creating document instance. Cause: 
org.xml.sax.SAXParseException; lineNumber: 74; columnNumber: 17;
``元素内容必须由格式正确的字符数据或标记组成。

方案一: 使用转义符。
方案二: 使用<![CDATA[ < ]]>

and (t1.status <![CDATA[ >= ]]> 1  and  t1.status <![CDATA[ <= ]]> 2)

--上述代码其实对应的sql:
and (t1.status > =1 andt1.status <= 2)