Mybatis进阶

Mybatis入门

开发我的第一个MyBatis程序

  1. resources目录:

放在这个目录当中的,一般都是资源文件,配置文件。

直接放到resources目录下的资源,等同于放到了类

的根路径下。

  1. 开发步骤

  • 第一步:打包方式jar

  • 第二步:引入依赖

  • mybatis依赖

  • mysql驱动依赖

  • 第三步:编写mybatis核心配置文件:mybatis-config.xml

注意:

第一:这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。只是大家都采用这个名字。

第二:这个文件存放的位置也不是固定的,可以随意,但一般情况下,会放到类的根路径下。

mybatis-config.xml文件中的配置信息不理解没关系,先把连接数据库的信息修改以下即可。

其他的别动。

  • 第四步:编写XxxxMapper.xml文件

在这个配置文件当中编写SQL语句。

这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.xml

把它暂时放到类的根路径下。

  • 第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:

  • 第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)

在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?

SqlSession

SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。

要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。

怎么获取SqlSessionFactory对象呢?

需要首先获取SqlSessionFactoryBuilder对象。

通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。

mybatis的核心对象包括:

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession

  1. 从 XML 中构建 SqlSessionFactory

通过官方的这句话,你能想到什么呢?

第一:在MyBatis中一定是有一个很重要的对象,这个对象是:SqlSessionFactory对象。

第二:SqlSessionFactory对象的创建需要XML。

XML是什么?

它一定是一个配置文件。

  1. mybatis中有两个主要的配置文件:

其中一个是:mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息等。(一个)

另一个是:XxxxMapper.xml,这个文件是专门用来编写SQL语句的配置文件。(一个表一个)

t_user表,一般会对应一个UserMapper.xml

t_student表,一般会对应一个StudentMapper.xml

  1. 关于第一个程序的小细节

  • mybatis中sql语句的结尾";"可以省略。

  • Resources.getResourceAsStream

小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)

优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。

  • InputStream is = new FileInputStream("d:\mybatis-config.xml");

采用这种方式也可以。

缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。

  • 已经验证了:

mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其它名字。

mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其它位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。

  • InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");

ClassLoader.getSystemClassLoader() 获取系统的类加载器。

系统类加载器有一个方法叫做:getResourceAsStream

它就是从类路径当中加载资源的。

通过源代码分析发现:

InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

底层的源代码其实就是:

InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");

  • CarMapper.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗?

都不是固定的。

resource属性:这种方式是从类路径当中加载资源。

url属性:这种方式是从绝对路径当中加载资源。

不建议,移植性差

  1. 关于mybatis的事务管理机制。(深度剖析)

  • 在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理

  • type属性的值包括两个:

JDBC(jdbc)

MANAGED(managed)

type后面的值,只有以上两个值可选,不区分大小写。

  • 在mybatis中提供了两种事务管理机制:

第一种:JDBC事务管理器

第二种:MANAGED事务管理器

  • JDBC事务管理器:

mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:

conn.setAutoCommit(false); 开启事务。

....业务处理...

conn.commit(); 手动提交事务

使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。

如果你编写的代码是下面的代码:

SqlSession sqlSession = sqlSessionFactory.openSession(true);

表示没有开启事务。因为这种方式压根不会执行:conn.setAutoCommit(false);

在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。

如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。

这种方式实际上是不建议的,因为没有开启事务

  • MANAGED事务管理器:

mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如:spring。

我不管事务了,你来负责吧。

对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED

那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。

没有人管理事务就是没有事务。也就是不会开启事务,不开启事务就会自动提交。

  • JDBC中的事务:

如果你没有在JDBC代码中执行:conn.setAutoCommit(false);的话,默认的autoCommit是true。

  • 重点:

以后注意了,只要你的autoCommit是true,就表示没有开启事务。

只有你的autoCommit是false的时候,就表示开启了事务。

  1. 关于mybatis集成日志组件。让我们调试起来更加方便。

  • mybatis常见的集成的日志组件有哪些呢?

SLF4J(沙拉风):沙拉风是一个日志标准,其中有一个框架叫做logback,它实现了沙拉风规范。

LOG4J

LOG4J2

STDOUT_LOGGING:标准日志

....

注意:log4j log4j2 logback都是同一个作者开发的。

  • 其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。

只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。

这个标签在编写的时候要注意,它应该出现在environments标签之前。注意顺序。当然,不需要记忆这个顺序。

因为有dtd文件进行约束呢。我们只要参考dtd约束即可。

这种实现也是可以的,可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。

但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。

  • 集成logback日志框架。

logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)

第一步:引入logback的依赖。

ch.qos.logback

logback-classic

1.2.11

第二步:引入logback所必须的xml配置文件。

这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。

这个配置文件必须放到类的根路径下。不能是其他位置。

主要配置日志输出相关的级别以及日志具体的格式。

mybatis入门案例:

package com.powernode.mybatis.test;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.FileInputStream;
import java.io.InputStream;

public class MyBatisIntroductionTest {
    public static void main(String[] args) throws Exception {

        // 获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

        // 获取SqlSessionFactory对象
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // Resources.getResourceAsStream默认就是从类的根路径下开始查找资源。
        //InputStream is = Resources.getResourceAsStream("com/mybatis.xml");
        //InputStream is = new FileInputStream("d:\\mybatis-config.xml");

        //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 一般情况下都是一个数据库对应一个SqlSessionFactory对象。

        // 获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession(); // 如果使用的事务管理器是JDBC的话,底层实际上会执行:conn.setAutoCommit(false);
        // 这种方式实际上是不建议的,因为没有开启事务。
        //SqlSession sqlSession = sqlSessionFactory.openSession(true);

        // 执行SQL语句
        int count = sqlSession.insert("insertCar"); // 返回值是影响数据库表当中的记录条数。

        System.out.println("插入了几条记录:" + count);

        // 手动提交
        sqlSession.commit(); // 如果使用的事务管理器是JDBC的话,底层实际上还是会执行conn.commit();

    }
}

mybatis完整版案例

package com.powernode.mybatis.test;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisCompleteTest {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
            // 开启会话(底层会开启事务)
            sqlSession = sqlSessionFactory.openSession();
            // 执行SQL语句,处理相关业务
            int count = sqlSession.insert("insertCar");
            System.out.println(count);
            // 执行到这里,没有发生任何异常,提交事务。终止事务。
            sqlSession.commit();
        } catch (Exception e) {
            // 最好回滚事务
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            // 关闭会话(释放资源)
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

编写Mybatis工具类

package com.powernode.mybatis.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;

/**
 * MyBatis工具类
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionUtil {

    // 工具类的构造方法一般都是私有化的。
    // 工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
    // 为了防止new对象,构造方法私有化。
    private SqlSessionUtil(){}

    private static SqlSessionFactory sqlSessionFactory;

    // 类加载时执行
    // SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象。
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /*public static SqlSession openSession(){
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // SqlSessionFactory对象:一个SqlSessionFactory对应一个environment,一个environment通常是一个数据库。
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }*/

    /**
     * 获取会话对象。
     * @return 会话对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

}

Mybatis的crud

在封装pojo类时,数据库表当中的字段应该和pojo类的属性一一对应。并且建议使用包装类,这样可以防止null的问题。

使用对象传参,底层调用的是get方法,所以如果没有提供get方法则会报错。通过反射机制获取到值。

使用mybatis完成CRUD

  1. 什么是CRUD

C: Create增

R: Retrieve查(检索)

U: Update改

D: Delete删

  1. insert

例如:JDBC的代码是怎么写的?

String sql = "insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)";

ps.setString(1, xxx);

ps.setString(2, yyy);

....

在JDBC当中占位符采用的是?,在mybatis当中是什么呢?

和?等效的写法是:#{}

在mybatis当中不能使用?占位符,必须使用 #{} 来代替JDBC当中的 ?

#{} 和 JDBC当中的 ? 是等效的。

java程序中使用Map可以给SQL语句的占位符传值:

Map<String, Object> map = new HashMap<>();

map.put("k1", "1111");

map.put("k2", "比亚迪汉");

map.put("k3", 10.0);

map.put("k4", "2020-11-11");

map.put("k5", "电车");

insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{k1},#{k2},#{k3},#{k4},#{k5});

注意:#{这里写什么?写map集合的key,如果key不存在,获取的是null}

一般map集合的key起名的时候要见名知意。

map.put("carNum", "1111");

map.put("brand", "比亚迪汉2");

map.put("guidePrice", 10.0);

map.put("produceTime", "2020-11-11");

map.put("carType", "电车");

insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});

java程序中使用POJO类给SQL语句的占位符传值:

Car car = new Car(null, "3333", "比亚迪秦", 30.0, "2020-11-11", "新能源");

注意:占位符#{},大括号里面写:pojo类的属性名

insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)

values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})

把SQL语句写成这个德行:

insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)

         values(null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})
 出现了什么问题呢?
     There is no getter for property named 'xyz' in 'class com.powernode.mybatis.pojo.Car'
     mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。
 
 怎么解决的?
     可以在Car类中提供一个getXyz()方法。这样问题就解决了。
 
 通过这个测试,得出一个结论:
     严格意义上来说:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
         写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。
         例如:getUsername() --> #{username}
         例如:getEmail() --> #{email}
         ....
 也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?
     调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()
  1. delete

  • 需求:根据id删除数据

将id=59的数据删除。

实现:

int count = sqlSession.delete("deleteById", 59);

delete from t_car where id = #{fdsfd}

注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。

  1. update

  • 需求:根据id修改某条记录。

实现:

update t_car set

          car_num=#{carNum},
          brand=#{brand},
          guide_price=#{guidePrice},
          produce_time=#{produceTime},
          car_type=#{carType}
     where
         id = #{id}
 </update>

 Car car = new Car(4L, "9999", "凯美瑞", 30.3, "1999-11-10", "燃油车");
 int count = sqlSession.update("updateById", car);
  1. select(查一个,根据主键查询的话,返回的结果一定是一个。)

  • 需求:根据id查询。

实现:

select * from t_car where id = #{id}

Object car = sqlSession.selectOne("selectById", 1);

mybatis底层执行了select语句之后,一定会返回一个结果集对象:ResultSet

JDBC中叫做ResultSet,接下来就是mybatis应该从ResultSet中取出数据,封装java对象。

所以你需要告诉Mybatis要封装成什么对象

需要特别注意的是:

select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成什么类型的java对象。你需要告诉mybatis。

resultType通常写的是:全限定类名。

Car{id=1, carNum='null', brand='宝马520Li', guidePrice=null, produceTime='null', carType='null'}

输出结果有点不对劲:

id和brand属性有值。

其他属性为null。

carNum以及其他的这几个属性没有赋上值的原因是什么?

select * from t_car where id = 1

执行结果:

+----+---------+-----------+-------------+--------------+----------+

| id | car_num | brand | guide_price | produce_time | car_type |

+----+---------+-----------+-------------+--------------+----------+

| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |

+----+---------+-----------+-------------+--------------+----------+

car_num、guide_price、produce_time、car_type这是查询结果的列名。

这些列名和Car类中的属性名对不上。

Car类的属性名:

carNum、guidePrice、produceTime、carType

那这个问题怎么解决呢?

select语句查询的时候,查询结果集的列名是可以使用as关键字起别名的。

select

         id,car_num as carNum,brand,guide_price as guidePrice,
         produce_time as produceTime,
         car_type as carType
     from
         t_car
     where
         id = #{id}
 </select>
 起别名之后:
 +----+--------+-----------+------------+-------------+---------+
 | id | carNum | brand     | guidePrice | produceTime | carType |
 +----+--------+-----------+------------+-------------+---------+
 |  1 | 1001   | 宝马520Li |      10.00 | 2020-10-11  | 燃油车  |
 +----+--------+-----------+------------+-------------+---------+
  1. select(查所有的)

List cars = sqlSession.selectList("selectAll");

注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。

selectList方法:mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。

  1. 在sql mapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。

怎么用?

在xml文件中:

         select
             id,car_num as carNum,brand,guide_price as guidePrice,
             produce_time as produceTime,
             car_type
         from
             t_car
     </select>

在java程序中的写法:

List cars = sqlSession.selectList("aaaaaaaaa.selectAll");

实际上,本质上,mybatis中的sqlId的完整写法:

namespace.id

命名空间+id唯一确定一条sql语句

注意点:Mybatis的build方法可以通过环境id来指定环境,如果不指定的话就使用mybatis的默认环境

properties标签

<!--java.util.Properties类,是一个Map集合,key和value都是String类型-->
<!--在properties标签可以配置很多属性-->
<properties>
    <!--这是其中的一个属性-->
    <!--<property name="属性名" value="属性值" />-->
	<property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
    ...
</properties>

<!--引入配置文件-->
<!--resource,一定是从类路径下查找资源-->
<properties resource="文件路径"/>

<!--从绝对路径中加载资源。绝对路径怎么写?file:///路径-->
<properties url="file:///d:/jdbc.properties"/>

动态SQL

<?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.powernode.mybatis.mapper.CarMapper">

    <delete id="deleteByIds2">
        delete from t_car where
        <foreach collection="ids" item="id" separator="or">
            id=#{id}
        </foreach>
    </delete>

    <insert id="insertBatch">
        insert into t_car values
        <foreach collection="cars" item="car" separator=",">
            (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
        </foreach>
    </insert>

    <!--
        foreach标签的属性:
            collection:指定数组或者集合
            item:代表数组或集合中的元素
            separator:循环之间的分隔符
            open: foreach循环拼接的所有sql语句的最前面以什么开始。
            close: foreach循环拼接的所有sql语句的最后面以什么结束。

        collection="ids" 第一次写这个的时候报错了,错误信息是:[array, arg0]
        什么意思?
            map.put("array", 数组);
            map.put("arg0", 数组);
    -->
    <delete id="deleteByIds">
        <!--
        delete from t_car where id in(
        <foreach collection="ids" item="aaaaaaa" separator=",">
            #{aaaaaaa}
        </foreach>
        )
        -->
        delete from t_car where id in
		<!-- 小括号可以省略不写 -->
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

    <select id="selectByChoose" resultType="Car">
        select * from t_car
        <where>
            <choose>
                <when test="brand != null and brand != ''">
                    brand like "%"#{brand}"%"
                </when>
                <when test="guidePrice != null and guidePrice != ''">
                    guide_price > #{guidePrice}
                </when>
                <otherwise>
                    car_type = #{carType}
                </otherwise>
            </choose>
        </where>
    </select>

    <update id="updateBySet">
        update t_car
        <set>
            <if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
            <if test="brand != null and brand != ''">brand = #{brand},</if>
            <if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
            <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
            <if test="carType != null and carType != ''">car_type = #{carType},</if>
        </set>
        where
            id = #{id}
    </update>

    <update id="updateById">
        update t_car set
            car_num = #{carNum},
            brand = #{brand},
            guide_price = #{guidePrice},
            produce_time = #{produceTime},
            car_type = #{carType}
        where
            id = #{id}
    </update>

    <select id="selectByMultiConditionWithTrim" resultType="Car">
        select * from t_car
        <!--
            prefix:加前缀
            suffix:加后缀
            prefixOverrides:删除前缀
            suffixOverrides:删除后缀
        -->
        <!--prefix="where" 是在trim标签所有内容的前面添加 where-->
        <!--suffixOverrides="and|or" 把trim标签中内容的后缀and或or去掉-->
        <trim prefix="where" suffixOverrides="and|or">
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%" or
            </if>
            <if test="guidePrice != null and guidePrice != ''">
                guide_price > #{guidePrice} and
            </if>
            <if test="carType != null and carType != ''">
                car_type = #{carType}
            </if>
        </trim>

    </select>

    <select id="selectByMultiConditionWithWhere" resultType="Car">
        select * from t_car
        <!--where标签是专门负责where子句动态生成的。-->
		<!-- where标签的作⽤:让where⼦句更加动态智能。
		所有条件都为空时,where标签保证不会⽣成where⼦句。
		⾃动去除某些条件前⾯多余的and或or。 
		where标签不能把后面的and去掉-->
        <where>
            <if test="brand != null and brand != ''">
                and brand like "%"#{brand}"%"
            </if>
            <if test="guidePrice != null and guidePrice != ''">
                and guide_price > #{guidePrice}
            </if>
            <if test="carType != null and carType != ''">
                and car_type = #{carType}
            </if>
        </where>
    </select>

    <select id="selectByMultiCondition" resultType="Car">
        select * from t_car where 1 = 1
        <!--
            1. if标签中test属性是必须的。
            2. if标签中test属性的值是false或者true。
            3. 如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。
            4. test属性中可以使用的是:
                当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param("brand"),那么这里只能使用brand
                当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2....
                当使用了POJO,那么test中出现的是POJO类的属性名。
            5. 在mybatis的动态SQL当中,不能使用&&,只能使用and。
        -->
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </select>

    <!-- sql标签⽤来声明sql⽚段
    include标签⽤来将声明的sql⽚段包含到某个sql语句当中
    作⽤:代码复⽤。易维护。  -->
            
    <sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_t
    ime produceTime,car_type carType</sql>
    <select id="selectAllRetMap" resultType="map">
     select <include refid="carCols"/> from t_car
    </select>
    <select id="selectAllRetListMap" resultType="map">
     select <include refid="carCols"/> carType from t_car
    </select>
    <select id="selectByIdRetMap" resultType="map">
     select <include refid="carCols"/> from t_car where id = #{id}
    </select>
     
</mapper>

高级映射

一张表的数据和JVM中的对象进行映射叫基本映射。

多张表和JVM中的对象进行映射叫高级映射。

多对一关系映射

<?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.powernode.mybatis.mapper.StudentMapper">

    <select id="selectByCidStep2" resultType="Student">
        select * from t_stu where cid = #{cid}
    </select>

    <!--
        分步查询的优点:
            第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
            第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。
        什么是延迟加载(懒加载),有什么用?
            延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
            作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
        在mybatis当中怎么开启延迟加载呢?
            association标签中添加fetchType="lazy"
            注意:默认情况下是没有开启延迟加载的。需要设置:fetchType="lazy"
            这种在association标签中配置fetchType="lazy",是局部的设置,只对当前的association关联的sql语句起作用。

        在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
            在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true

        实际开发中的模式:
            把全局的延迟加载打开。
            如果某一步不需要使用延迟加载,请设置:fetchType="eager"
    -->
    <!--两条SQL语句,完成多对一的分步查询。-->
    <!--这里是第一步:根据学生的id查询学生的所有信息。这些信息当中含有班级id(cid)-->
    <resultMap id="studentResultMapByStep" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
					<!-- 这里需要指定另外第二步SQL语句的id -->
                     select="com.powernode.mybatis.mapper.ClazzMapper.selectByIdStep2"
					 <!-- 这里是要传给第二步的SQL语句的参数 -->
                     column="cid"
                     fetchType="eager"/>
    </resultMap>

    <select id="selectByIdStep1" resultMap="studentResultMapByStep">
        select sid,sname,cid from t_stu where sid = #{sid}
    </select>

    <!--一条SQL语句,association。-->
    <resultMap id="studentResultMapAssociation" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <!--
            association:翻译为关联。一个Student对象关联一个Clazz对象
                property:提供要映射的POJO类的属性名。
                javaType:用来指定要映射的java类型。
        -->
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>
    </resultMap>

    <select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_stu s left join t_clazz c on s.cid = c.cid
        where
            s.sid = #{sid}
    </select>

    <!--多对一映射的第一种方式:一条SQL语句,级联属性映射。-->
    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <result property="clazz.cid" column="cid"/>
        <result property="clazz.cname" column="cname"/>
    </resultMap>

    <select id="selectById" resultMap="studentResultMap">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_stu s left join t_clazz c on s.cid = c.cid
        where
            s.sid = #{sid}
    </select>

</mapper>

一对多关系映射

<?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.powernode.mybatis.mapper.ClazzMapper">

    <!--分步查询第一步:根据班级的cid获取班级信息。-->
    <resultMap id="clazzResultMapStep" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus"
                    select="com.powernode.mybatis.mapper.StudentMapper.selectByCidStep2"
                    column="cid" fetchType="eager" />
    </resultMap>

    <select id="selectByStep1" resultMap="clazzResultMapStep">
        select cid,cname from t_clazz where cid = #{cid}
    </select>

    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!--一对多,这里是collection。collection是集合的意思。-->
		<!-- property:java对象的属性名 -->
        <!--ofType 属性用来指定集合当中的元素类型。-->
        <collection property="stus" ofType="Student">
            <id property="sid" column="sid"/>
            <result property="sname" column="sname"/>
        </collection>
    </resultMap>

    <select id="selectByCollection" resultMap="clazzResultMap">
        select c.cid,c.cname,s.sid,s.sname from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
    </select>


    <!--分步查询第二步:根据cid获取班级信息。-->
    <select id="selectByIdStep2" resultType="Clazz">
        select cid,cname from t_clazz where cid = #{cid}
    </select>

</mapper>

缓存

缓存是一种优化的操作,它可以减少我们的IO操作和查找算法,缓存是针对DQL语句的,即select语句

一级缓存

mybatis的一级缓存是默认开启的,不需要做额外的配置,只要使用同一个sqlSession对象执行同一条sql语句,默认就会走缓存,一级缓存是存在sqlSession对象里的

try {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
            SqlSession sqlSession = factory.openSession();
            StudentDao studentDao1 = (StudentDao) sqlSession.getMapper(StudentDao.class);
            Student student1 = studentDao1.selectById(1);
            System.out.println(student1);
            StudentDao studentDao2 = sqlSession.getMapper(StudentDao.class);
            Student student2 = studentDao2.selectById(1);
            System.out.println(student2);
        } catch (IOException e) {
            e.printStackTrace();
        }

什么时候不走缓存

1.SqlSession对象不是同一个,肯定不走缓存,因为一级缓存就是保存SqlSession对象中的
2.查询条件不一样,肯定也不走缓存,因为缓存中根本就没有它的缓存

什么时候一级缓存失效

1.执行了sqlSession的clearCache()方法,这是手动清空缓存
2.执行了INSERT,UPDATE,DELETE语句,不管你是操作哪张表的,都会清空一级缓存
	为什么这样设计?
	为了保证数据的准确性
try {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
            SqlSession sqlSession = factory.openSession();
            StudentDao studentDao1 = (StudentDao) sqlSession.getMapper(StudentDao.class);
            Student student1 = studentDao1.selectById(1);
            System.out.println(student1);

            //手动清空缓存
            sqlSession.clearCache();

            StudentDao studentDao2 = sqlSession.getMapper(StudentDao.class);
            Student student2 = studentDao2.selectById(1);
            System.out.println(student2);
        } catch (IOException e) {
            e.printStackTrace();
        }
二级缓存
使用二级缓存的步骤:
	1.在需要使用二级缓存的sqlMapper.xml文件中添加配置:<cache />
	2.使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
	3.SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才能用。
	当SqlSession对象关闭后,之前存在一级缓存中的数据就会放到二级缓存中
<!--
	默认情况下,二级缓存机制是开启的。
	只需要在对应的SqlMapper.xml文件中添加以下标签。用来表示“我”使用二级缓存
	<cache />
-->
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
        SqlSession sqlSession1 = factory.openSession();
        StudentDao studentDao1 = sqlSession1.getMapper(StudentDao.class);
        SqlSession sqlSession2 = factory.openSession();
        StudentDao studentDao2 = sqlSession2.getMapper(StudentDao.class);

        //这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存)
        Student student1 = studentDao1.selectById(1);
        System.out.println(student1);
        //这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession2是一级缓存)
        //程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存中
        sqlSession1.close();

        Student student2 = studentDao2.selectById(1);
        System.out.println(student2);

        //程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存中
//        sqlSession1.close();
        //程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存中
        sqlSession2.close();

从二级缓存中取数据的前提是一级缓存已经关闭了,一级缓存的优先级比较高,先从一级缓存中找

二级缓存的失效

只要两次查询之间出现了增删改操作,二级缓存就会失效。【一级缓存也会失效】

二级缓存的相关配置

下面的这些都是cache标签的属性:

缓存就是往里面放查询出来的数据,这些数据是有向后顺序等属性的,为了不存储不必要的信息,所以它里面有驱逐算法
1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。
a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其
实还有⼀种淘汰算法LFU,最不常⽤。)
b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。
c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
2. flushInterval:
a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀
直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。(二级缓存一刷新缓存就被清空了,即失效了)
3. readOnly:
a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能
会存在安全问题。
b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安
全。
4. size:
a. 设置⼆级缓存中最多可存储的java对象数量。默认值1024。
Mybatis集成EhCache
步骤:
1.引入mybatis-ehcache依赖
2.写ehcace.xml配置文件
3.在sqlMapper.xml文件中添加<cache type="...ehcache" />
4.写测试代码

这个不需要死记硬背,用到的时候看文档一步一步来就行了

集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常⻅,按照以下步骤操
作,就可以完成集成:

第⼀步:引⼊mybatis整合ehcache的依赖。

!--mybatis集成ehcache的组件-->
<dependency>
 <groupId>org.mybatis.caches</groupId>
 <artifactId>mybatis-ehcache</artifactId>
 <version>1.2.2</version>
</dependency>
<!--ehcache需要slf4j的⽇志组件,log4j不好使-->
<dependency>
 <groupId>ch.qos.logback</groupId>
 <artifactId>logback-classic</artifactId>
 <version>1.2.11</version>
 <scope>test</scope>
</dependency>

第⼆步:在类的根路径下新建echcache.xml⽂件,并提供以下配置信息。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
 updateCheck="false">
 <!--磁盘存储:将缓存中暂时不使⽤的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
 <diskStore path="e:/ehcache"/>
 
 <!--defaultCache:默认的管理策略-->
 <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有
效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
 <!--maxElementsInMemory:在内存中缓存的element的最⼤数⽬-->
 <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
 <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-
->
 <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多⻓时间没有被访问就会失
效。只对eternal为false的有效。默认值0,表示⼀直可以访问-->
 <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。
只对eternal为false的有效。默认值0,表示⼀直可以访问-->
 <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
 <!--FIFO:first in first out (先进先出)-->
 <!--LFU:Less Frequently Used (最少使⽤).意思是⼀直以来最少被使⽤的。缓存的元
素有⼀个hit 属性,hit 值最⼩的将会被清出缓存-->
 <!--LRU:Least Recently Used(最近最少使⽤). (ehcache 默认值).缓存的元素有⼀
个时间戳,当缓存容量满了,⽽⼜需要腾出地⽅来缓存新的元素的时候,那么现有缓存元素中时间戳
离当前时间最远的元素将被清出缓存-->
 <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDis
k="false" diskPersistent="false"
 timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStor
eEvictionPolicy="LRU"/>
</ehcache>

第三步:修改SqlMapper.xml⽂件中的标签,添加type属性。

<!--集成EhCache组件-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/

第四步:编写测试程序使⽤。

@Test
public void testSelectById2() throws Exception{
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().b
uild(Resources.getResourceAsStream("mybatis-config.xml"));
 
 SqlSession sqlSession1 = sqlSessionFactory.openSession();
 CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
 Car car1 = mapper1.selectById(83L);
 System.out.println(car1);
 
 sqlSession1.close();
 
 SqlSession sqlSession2 = sqlSessionFactory.openSession();
 CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
 Car car2 = mapper2.selectById(83L);
 System.out.println(car2);
}

逆向工程

所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类
等。
要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。

思考:使⽤这个插件的话,需要给这个插件配置哪些信息?
pojo类名、包名以及⽣成位置。
SqlMapper.xml⽂件名以及⽣成位置。
Mapper接⼝名以及⽣成位置。
连接数据库的信息。
指定哪些表参与逆向⼯程。
......
逆向工程就是一个组件,它可以根据数据库表来给你生成Java代码和xml配置文件
使用逆向工程的思路:
1.为了让它从数据库中获取信息生成Java代码,我们需要告诉它数据库的连接信息
	知道要操作哪张表后它可以生成代码了,但是它并不知道生成后要把代码放到哪里,所以我们需要明确的告诉它
2.类和xml文件生成到哪一个位置也需要告诉它
你想把信息告诉插件,告诉java代码只有通过配置文件

这个也不需要死记硬背,用的时候按照文档操作即可

第一步:在pom中添加逆向⼯程插件

<!--定制构建过程-->
<build>
 <!--可配置多个插件-->
 <plugins>
     
     
 <!--其中的⼀个插件:mybatis逆向⼯程插件-->
 <plugin>
 <!--插件的GAV坐标-->
 <groupId>org.mybatis.generator</groupId>
 <artifactId>mybatis-generator-maven-plugin</artifactId>
 <version>1.4.1</version>
 <!--允许覆盖-->
 <configuration>
 <overwrite>true</overwrite>
 </configuration>
 <!--插件的依赖-->
 <dependencies>
 <!--mysql驱动依赖(插件的依赖)-->
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.30</version>
 </dependency>
 </dependencies>
 </plugin>
     
     
 </plugins>
</build>

允许覆盖:逆向工程逆向生成java类以后,如果再次运行这个组件,那么它会把原先文件内容全部清空,然后再写入,如果不配的话,生成的东西是追加的方式

mysql驱动依赖:要通过逆向工程反向生成Java程序是需要连接数据库的,连接数据库需要使用Mysql的依赖

第三步:配置generatorConfig.xml

该⽂件名必须叫做:generatorConfig.xml

该⽂件必须放在类的根路径下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//E
N"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
 targetRuntime有两个值:
 MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。
 MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
 -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!--防⽌⽣成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersP
lugin"/>

        <!--注释信息的生成-->
        <commentGenerator>
            <!--是否去掉⽣成⽇期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/powerno
de"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- ⽣成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空⽩-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- ⽣成SQL映射⽂件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- ⽣成Mapper接⼝的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>

第四步:运⾏插件

通过generate命令运行插件

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值