MyBatis

MyBatis

一、 MyBatis 概述

  • MyBatis是Java中的一种数据持久层框架

  • 本质上就是对JDBC的封装,通过MyBatis完成CRUD

  • MyBatis属于半自动化ORM框架

  • ORM:对象关系映射

    • O(Object):Java虚拟机中的Java对象
    • R(Relational):关系型数据库
    • M(Mapping):将Java虚拟机中的Java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象

二、第一个MyBatis程序

  • MyBatis 中主要有两个配置文件
    • mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息
    • XxxMapper.xml,这个文件是专门用来编写sql语句的配置文件,一般是一个表对应一个

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>
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/ych"/>
               <property name="username" value="root"/>
               <property name="password" value="toor"/>
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <!--sql映射文件创建好之后(XxxMapper.xml),需要将该文件路径配置到这里-->
       <mapper resource="CarMapper.xml"/>
       <!-- url 属性 从绝对路径中加载程序,不建议使用-->
       <!-- <mapper url="绝对路径">  -->
   </mappers>
</configuration>

CarMapper.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先随意写一个-->
<mapper namespace="car">
   <!--insert sql:保存一个汽车信息-->
   <insert id="insertCar">
       insert into t_car
           (id,car_num,brand,guide_price,produce_time,car_type)
       values
           (null,'1003','丰田mirai',40.30,'2014-10-05','氢能源')
   </insert>
</mapper>

MyBatisIntroductionTest

package com.ych.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.IOException;
import java.io.InputStream;

public class MyBatisIntroductionTest {
    public static void main(String[] args) throws IOException {
        // 获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 获取SqlsessionFactory 对象
        InputStream resourceAsStream = Resources.getResourceAsStream("mybaits-config.xml");
        // 一般情况下是 一个数据库对应一个sqlsessionfactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

        // 获取SqlSession 对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 执行sql语句
        int insertCar = sqlSession.insert("insertCar");

        System.out.println("插入了"+insertCar+"条数据");

        // 手动提交
        sqlSession.commit();
        sqlSession.close();

    }
  • 第一个程序中的小细节

    • mybatis中sql语句的结尾";"可以省略
    • Resource.getResourceAsStream
      • 小技巧:凡是遇到resource,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载的
      • 优点:项目的移植性很强
  • 关于mybatis的事务管理机制

    • 在mybatis-config.xml文件中,可以通过以下配置进行mybatis的事务管理,<transactionManager type="type"/>
    • type属性的值包括两个
      • JDBC和MANAGED
    • 在mybatis中提供两种事务管理机制
      • JDBC事务管理器
        • mybatis框架自己管理事务,自己采用原生JDBC代码区管理事务
      • MANAGED事务管理机制
        • mybatis不再负责事务的管理,而是交给其它容器区负责,例如:Spring
    • 🌟当autoCommit是true时,就表示没有开启事务
  • Junit 单元测试

// 测试用例
// 名字规范:你要测试的类名+Test
public class CarMapperTest{
    // 一般是一个业务方法对应于一个测试方法
    // 测试方法的规范,以test开始, public void testXxxx(){}
    // 测试方法
    @Test      // @Test注解非常重要,被这个注解标注的方法就是一个单元测试方法
    public void testInsert(){
        // 单元测试中两个非常重要的值
        // 一个是 实际值(被测试的业务方法的真正执行结果)
        // 一个是 期望值(被执行的业务方法的期望结果)
        // 获取实际值和期望值
        int actual = XXXX.insert();
        int expected = xxx;
        // 加断言进行测试
        Assert.assertEquals(excepted,actual);
    }
    
    @Test
    public void testUpdate(){}
    
}
  • Mybatis 继承日志组件
    • 引入logback的依赖
    • 引入logback所必须得xml配置文件
      • 文件名是logback.xml或者logback-test.xml,不能是其它名字
      • 必须放在根目录下

三、CRUD

1. 增加
  • 在mybatis中使用#{}作为占位符
insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{k1},#{k2},#{k3},#{k4},#{k5})
// 变量命名最好是见名知意,这里命名并不规范
  • 使用map对sql语句进行传值
Map<String,Object> map = new HashMap<>();
map.put("k1","1011");
map.put("k2","吉利");
map.put("k3",11.98);
map.put("k4","2022-10-11");
map.put("k5","混沌");

int insertCar = sqlSession.insert("insertCar", map);
  • 使用POJO类给sql语句传值

mybatis-config.xml

  • {}中的car类中的属性名
<insert id="insertCar">
    insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

CarMapperTest

@Test
public void testInsertCarByPojo(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // 封装数据
    Car car = new Car(null, "3333", "海豹06", 9.98, "2024-03-11", "混合动力");
    int insertCar = sqlSession.insert("insertCar", car);
    System.out.println(insertCar);
    // 执行sql
    sqlSession.commit();
    sqlSession.close();
}
2. 删除

mybatis-config.xml

<delete id="deleteCar">
    # 一个变量就可以随便起名字
    delete from t_car where id = #{id}
</delete>

CarMapperTest

@Test
public void testDeleteCar(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    int deleteCar = sqlSession.delete("deleteCar", 9);
    System.out.println(deleteCar);
    sqlSession.commit();
    sqlSession.close();
}
3. 修改

mybatis-config.xml

<update id="updateCar">
    update t_car set guide_price = #{guidePrice} where id = #{id};
</update>

CarMapperTest

@Test
public void testUpdateCar(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // 封装数据
    Car car = new Car(1L, "", "", 9.98, "", "");
    int insertCar = sqlSession.update("updateCar", car);
    System.out.println(insertCar);
    // 执行sql
    sqlSession.commit();
    sqlSession.close();
}
4. 查询
  • 查询单个

mybatis-config.xml

<!-- resultType 设置查询内容要转换的类型-->
<select id="selectById" resultType="com.ych.mybatis.pojo.Car">
    <!-- as 是起别名,为了使数据库和car类中的变量名保持一致-->
    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>

CarMapperTest

@Test
public void testSelectById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    Object car = sqlSession.selectOne("selectById", 1L);
    System.out.println(car);
    sqlSession.close();
}
  • 查所有

mybatis-config.xml

<select id="selectAll" resultType="com.ych.mybatis.pojo.Car">
        select id, car_num as carNum ,brand, guide_price as guidePrice,
               produce_time as produceTime, car_type as carType
        from t_car
</select>

CarMapperTest

@Test
public void testSelectAll(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    List<Object> cars = sqlSession.selectList("selectAll");
    System.out.println(cars);
    sqlSession.close();
}
4. namespace 命名空间
  • 在sql XxxMapper.xml 文件当中有一个namespace,这个属性用于指定命名空间,用来防止id重复
  • 在java程序中,mybatis的sqlId完整的写法是:namespace.Id

四、MyBatis核心配置文件详解

1. environments
  • 一个SqlSessionFactory对象对应一个数据库表
  • 一个SqlSessionFactory对应一个环境environment

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>
    <!--默认使用开发环境-->
    <!--<environments default="powernode">-->
    <!--默认使用生产环境-->
    <environments default="powernode">
        <!--开发环境-->
        <environment id="powernode">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <!--生产环境-->
        <environment id="production">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
 </mappers>
</configuration>

ConfigurationTest.testEnvironment

// 使用默认数据库
SqlSessionFactory sqlSessionFactorysqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession(true);
int count = sqlSession.insert("insertCar", car);
System.out.println("插入了几条记录:" + count);

// 使用指定数据库
SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "powernode"); //指定数据库为“powernode”
SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
int count1 = sqlSession1.insert("insertCar", car);
System.out.println("插入了几条记录:" + count1);
2. transactionManager

<transactionManager type="[JDBC|MANAGED]"/>

  • 当事务管理器是:JDBC
    • 采用JDBC的原生事务机制:
      • 开启事务:conn.setAutoCommit(false);
      • 处理业务…
      • 提交事务:conn.commit();
  • 当事务管理器是:MANAGED
    • 交给JavaEE容器去管理事务,但目前使用的是本地程序,没有容器的支持,当mybatis找不到容器的支持时:没有事务。也就是说只要执行一条DML语句,则提交一次。
3. dataSource
  • 作用:为程序提供Connection对象

<dataSource type="[UNOOLED|POOLED|JNDI]">

  • UNPOOLED:不使用数据库连接池技术,每一个请求过来之后,都是创建新的Connection对象
  • POOLED:使用mybatis自己实现的数据库连接池
  • JNDI:集成其它第三方的数据库连接池
  • 其余重要属性:

    • poolMaximumActiveConnections:最大的活动的连接数量。默认值10
    • poolMaximumIdleConnections:最大的空闲连接数量。默认值5
    • poolMaximumCheckoutTime:强行回归池的时间。默认值20秒。
    • poolTimeToWait:当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的)
    <!--最大连接数-->
    <property name="poolMaximumActiveConnections" value="3"/>
    <!--这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。-->
    <property name="poolTimeToWait" value="20000"/>
    <!--强行回归池的时间-->
    <property name="poolMaximumCheckoutTime" value="20000"/>
    <!--最多空闲数量-->
    <property name="poolMaximumIdleConnections" value="1"/>
    
4. properties
  • 连接数据库的信息可以单独写到一个属性资源文件.properties

jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode

mybatis-config4.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 resource="jdbc.properties">
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="root"/>
    </properties>

    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--${key}使用-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
5. mapper
  • mapper标签用来指定SQL映射文件的路径
  • 不同的sql文件,可以有多个mapper标签

五、手写MyBatis框架

  • 先不学

六、在WEB中应用MyBatis(使用MVC架构模式)

七、使用javassist生成类(了解)

八、MyBatis中接口代理机制及使用

  • 使用mybatis获取dao接口代理类对象
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
  • 使用以上代码的前提是:AccountMapper.xml文件中的namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致
<?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.ych.bank.dao.AccountDao">  <!--与dao接口的全限定名称一致-->
    <select id="selectByActno" resultType="com.ych.bank.pojo.Account"> <!--与dao接口中方法名一致-->
        select * from t_act where actno = #{actno}
    </select>

    <update id="updateByActno"> <!--与dao接口中方法名一致-->
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
</mapper>
  • accountServiceImpl
package com.ych.bank.service.impl;

import com.ych.bank.com.ych.bank.exceptions.MoneyNotEnoughtException;
import com.ych.bank.com.ych.bank.exceptions.TransferException;
import com.ych.bank.dao.AccountDao;
import com.ych.bank.pojo.Account;
import com.ych.bank.service.AccountService;
import com.ych.bank.utils.SqlSessionUtil;

public class AccountServiceImpl implements AccountService {
    
    // 获取dao对象,使用getMapper方法
    private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
//    private AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughtException, TransferException {
        // 1. 判断转出账户余额是否充足(select)
        Account fromAct = accountDao.selectByActno(fromActno);
        // 2. 如果转出账户余额不足,提示用户
        if (fromAct.getBalance() < money){
            throw new MoneyNotEnoughtException("余额不足");
        }
        // 3. 如果转出账户余额充足,更新转出账号余额(update)
        //先更新内存中java对象的account余额
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);
        // 4. 更新转入账户余额(update)

        int count = accountDao.updateByActno(fromAct);
        count += accountDao.updateByActno(toAct);
        if (count != 2) {
            throw new TransferException("转账异常");
        }

    }
}

九、MyBatis小技巧

9.1 #{}和${}\
  • #{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
  • ${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到
  • 如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句中的
  • 需求:根据produceTime,按升序返回查询结果
<select id="selectAllByProduceTime" resultType="com.ych.mybatis.pojo.Car">
 select id,
 	car_num as carNum,
 	brand,
 	guide_price as guidePrice,
 	produce_time as produceTime,
 	car_type as carType
     from t_car
 order by produce_time ${AscOrDeasc}
</select>
  • 模糊查询
  • 需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。
  • 使用${}
<select id="selectLikeByBrand" resultType="Car">
  select
  id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
  from
  t_car
  where
  brand like '%${brand}%'
</select>
  • 使用#{}
<select id="selectLikeByBrand" resultType="Car">
  select
  id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
  from
  t_car
  where
  brand like concat('%',#{brand},'%')
</select>
<select id="selectLikeByBrand" resultType="Car">
  select
  id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
  from
  t_car
  where
  brand like "%"#{brand}"%"
</select>
9.2 typeAliases(MyBatis别名机制)
  • 首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
  • typeAliases标签中的typeAlias可以写多个。
  • typeAlias:
    • type属性:指定给哪个类起别名
    • alias属性:别名。
      • alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
      • alias是大小写不敏感的。也就是说假设alias=“Car”,再用的时候,可以CAR,也可以car,也可以Car,都行。
  • 第一种:自定义别名
  • mybatis-config.xml
<typeAliases>
<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
</typeAliases>
  • 第二种:默认机制
<typeAliases>
<!-- 用类名作为别名,等价于 alias = "Car"-->
<typeAlias type="com.powernode.mybatis.pojo.Car"/>
</typeAliases>
  • 第三种:package

  • mybatis用提供package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。

  • mybatis-config.xml
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
</typeAliases>
  • 该包下所有的类都将用简类名作为别名
9.3 插入数据时,获取自动生成的主键

CarMapper.xml

<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
  • useGeneratedKeys="true":获得自动生成的主键
  • keyProperty="id":将获取的主键绑定到类的id属性上

十、MyBatis参数处理

1. 单个简单类型参数
  • 简单类型包括:
    • byte short int long float double char
    • Byte Short Integer Long Float Double Character
    • String
    • java.util.Date
    • java.sql.Dat
  • SQL映射文件中的配置比较完整的写法是
<select id="selectByName" resultType="student" parameterType="java.lang.String">
  select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
  • 其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
    • javaType:可以省略
    • jdbcType:可以省略
    • parameterType:可以省略
2. Map参数
3. 实体类参数
4. 多参数

StudentMapper.xml

<select id="selectByNameAndSex" resultType="student">
<!--select * from t_student where name = #{name} and sex = #{sex}-->
select * from t_student where name = #{arg0} and sex = #{arg1}
</select>
  • 多个参数时,mybatis会默认将参数依次命名为arg0,arg1…或者param0,param1…,顺序与接口中方法的参数顺序一致
5. @Param注解
  • 多个参数时,可以是@Param参数自定义参数名

StudentMapper.xml

<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{name} and sex = #{sex}
</select>

StudentMapper接口

/**
* 根据name和age查询
* @param name
* @param age 
* @return
*/
List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);

十一、MyBatis查询语句专题

  • resultMap结果映射
  • 查询结果的列名和java对象的属性名对应不上怎么办?
    • 第一种方式:as 给列起别名
    • 第二种方式:使用resultMap进行结果映射
    • 第三种方式:是否开启驼峰命名自动映射(配置settings)
1. 使用resultMap进行结果映射

CarMapper.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.ych.mybatis.mapper.CarMapper">
 <!--
    1. 专门定义一个结果映射,在这个结果映射当中指定数据库表的字段名和java类的属性名的对应关系
    2. type属性:用来指定pojo类的类名
    3. id属性:指定resultMap的唯一标识,这个id将来要作为select标签中使用
    -->
    <resultMap id="carResultMap" type="com.ych.mybatis.pojo.Car">
        <!--指定主键-->
        <id property="id" column="id"></id>
        <!--property后面填写pojo属性类的属性名,column后面填写数据库表的字段名-->
        <!--如果属性名和数据库中的字段本身就是一致的,则可以省略,但id最好不要省略-->
        <result property="carNum" column="car_num"/>
        <result property="brand" column="brand"/>
        <result property="guidePrice" column="guide_price"/>
        <result property="produceTime" column="produce_time"/>
        <result property="carType" column="car_type"/>
    </resultMap>

    <!--select 标签的resultMap属性,用来指定使用哪个结果映射,resultMap后面的值是resultMap的id -->
    <select id="selectAllByRetMap" resultMap="carResultMap">
        select * from t_car
    </select>
</mapper>

CarMapperTest

@Test
public void testSelectAllByRetMap(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectAllByRetMap();
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}
2. 开启驼峰命名自动映射
  • 使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
  • Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
  • SQL命名规范:全部小写,单词之间采用下划线分割
  • 注意单词别拼错:mapUnderscoreToCamelCase
实体类中的属性名数据库表的列名
carNumcar_num
carTypecar_type
produceTimeproduce_time

mybatis-config.xml

<!--放在properties标签后面-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

CarMapper.xml

<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select * from t_car
</select>

CarMapperTest.testSelectAllByMapUnderscoreToCamelCase

@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase();
System.out.println(cars);
}

十二、动态SQL

1. if标签

CarMapper.xml

<select id="selectByMultiCondition" resultType="com.ych.mybatis.pojo.Car">
	select * from t_car where 1=1
 <!--if标签动态拼接sql语句-->
 <if test="brand != null and brand != ''">
     and brand like "%"#{brand}"%"
 </if>
 <if test="guide_price !=null and guide_price != ''">
     and guide_price > #{guide_price}
 </if>
 <if test="car_type != null and car_type != ''">
     and car_type = #{car_type}
 </if>
</select>
2. where标签
  • where标签的作用:让where子句更加动态智能。

    • 所有条件都为空时,where标签保证不会生成where子句。

    • 自动去除某些条件前面多余的and或or。

CarMapper.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.ych.mybatis.mapper.CarMapper">
 <select id="selectByMultiConditionWithWhere" resultType="com.ych.mybatis.pojo.Car">
     select * from t_car
     <!-- 1. 所有条件都为空时,where标签保证不会生成where子句。
		    2. 自动去除某些条件**前面**多余的and或or。-->
        <where>
            <if test="brand != null and brand != ''">
                and brand like "%"#{brand}"%"
            </if>
            <if test="guide_price !=null and guide_price != ''">
                and guide_price > #{guide_price}
            </if>
            <if test="car_type != null and car_type != ''">
                and car_type = #{car_type}
            </if>
        </where>
    </select>
</mapper>
3. trim标签
  • trim标签的属性:

    • prefix:在trim标签中的语句前添加内容

    • suffix:在trim标签中的语句后添加内容

    • prefixOverrides:前缀覆盖掉(去掉)

    • suffixOverrides:后缀覆盖掉(去掉)

<select id="selectByMultiConditionWithTrim" resultType="car">
  select * from t_car
  <trim prefix="where" suffixOverrides="and|or">
    <if test="brand != null and brand != ''">
      brand like #{brand}"%" and
    </if>
    <if test="guidePrice != null and guidePrice != ''">
      guide_price >= #{guidePrice} and
    </if>
    <if test="carType != null and carType != ''">
      car_type = #{carType}
    </if>
  </trim>
</select>
5. set标签
  • 主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
  • 比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。
<update id="updateWithSet">
  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>
4. choose when otherwise
  • 只有一个分支会被选择

  • 这三个标签是在一起使用的:

<choose>
<when></when>
<when></when>
<when></when>
<otherwise></otherwise>
</choose>
  • 等价于
if(){

}else if(){

}else if(){

}else if(){

}else{

}
<select id="selectWithChoose" 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>
        produce_time >= #{produceTime}
      </otherwise>
    </choose>
  </where>
</select>
6. foreach标签
  • 用来执行批量删除或者批量插入
  • 批量删除
  • 第一种方式
  • delete from t_car where id in(1,2,3);

  • CarMapper.xml

<!--
collection:集合或数组
item:集合或数组中的元素
separator:分隔符
open:foreach标签中所有内容的开始
close:foreach标签中所有内容的结束
-->
<delete id="deleteBatchByForeach">
  delete from t_car where id in
  <foreach collection="ids" item="id" separator="," open="(" close=")">
    #{id}
  </foreach>
</delete>
  • 拼接的SQL

Preparing: delete from t_car where id in ( ? , ? , ? ) Parameters: 1(Long), 2(Long), 3(Long)

  • 第二种方式
  • delete from t_car where id = 1 or id = 2 or id = 3;
  • CarMapper.xml
<delete id="deleteBatchByForeach2">
  delete from t_car where
  <foreach collection="ids" item="id" separator="or">
    id = #{id}
  </foreach>
</delete>
  • 拼接的SQL

Preparing: delete from t_car where id = ? or id = ? or id = ? Parameters: 1(Long), 2(Long), 3(Long)

  • 批量插入
insert into t_car values (null,'1001','凯美瑞',35.0,'2010-10-11','燃油车'), (null,'1002','比亚迪唐',31.0,'2020-11-11','新能源'), (null,'1003','比亚迪宋',32.0,'2020-10-11','新能源')
  • CarMapper.xml
<insert id="insertBatchByForeach">
  insert into t_car values 
  <foreach collection="cars" item="car" separator=",">
    (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
  </foreach>
</insert>
  • 拼接的SQL

Preparing: insert into t_car values (null,?,?,?,?,?) , (null,?,?,?,?,?) , (null,?,?,?,?,?) Parameters: 1001(String), 凯美瑞2021(String), 32.0(Double), 2021-10-11(String), 燃油车(String), 1002(String), 凯美瑞2022(String), 33.0(Double), 2022-10-11(String), 燃油车(String), 1003(String), 凯美瑞2023(String), 35.0(Double), 2023-10-11(String), 燃油车(String)

7. sql标签与include标签
  • sql标签用来声明sql片段
  • include标签用来将声明的sql片段包含到某个sql语句当中
  • 作用:代码复用。易维护
<sql id="carCols">
    id,car_num carNum,brand,guide_price guidePrice,produce_time 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>

十三、MyBatis的高级映射及延迟加载

1. 多对一
  • 第一种方式:级联属性映射

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

 <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>
  • 第二种方式:associate

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

 <resultMap id="StudentResultMap" type="Student">
     <id property="sid" column="sid"/>
     <result property="sname" column="sname"/>
     <!--用association来关联另一张表-->
     <association property="clazz" javaType="Clazz">
         <id property="cid" column="cid"/>
         <result property="cname" column="cname"/>
     </association>
 </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>
  • 以上两种方式的sql语句都是:

Preparing: 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 = ? Parameters: 1(Integer)

  • 第三种方式:分布查询
  • 其他位置不需要修改,只需要修改以及添加以下三处
  • 第一处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条子sql语句的条件。

StudentMapper.xml

<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
            select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
            column="cid"/>
</resultMap>

<select id="selectBySid" resultMap="studentResultMap">
select s.* from t_student s where sid = #{sid}
</select>
  • 第二处:在ClazzMapper接口中添加方法

ClazzMapper接口

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
public interface ClazzMapper {
 Clazz selectByCid(Integer cid);
}

  • 第三处:在ClazzMapper.xml文件中进行配置

ClazzMapper.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.powernode.mybatis.mapper.ClazzMapper">
 <select id="selectByCid" resultType="Clazz">
     select * from t_clazz where cid = #{cid}
 </select>
</mapper>
  • sql执行过程
  • Preparing: select * from t_stu where sid = ? Parameters: 1(Integer)
  • Preparing: select * from t_clazz where cid = ? Parameters: 1001(Integer)
  • 分步优点:
    • 代码复用性增强。
    • 支持延迟加载。【暂时访问不到的数据可以先不查询。提高程序的执行效率。
2. 多对一延迟加载
  • 在mybatis-config.xml中打开全局延迟加载
<settings>
 <!--开启驼峰自动映射-->
 <setting name="mapUnderscoreToCamelCase" value="true"/>
 <!--开启全局延迟加载-->
 <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • 如果某一步不需要延迟加载,可以在XxxMapper.xml的association标签张宏关闭
<!-- 分布查询 -->
<resultMap id="StudentResultMap" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <association property="clazz"
                 select="com.ych.mybatis.mapper.ClazzMapper.selectByCid"
                 column="cid"
                 fetchType="eager"/>  <!--关闭延迟加载-->
</resultMap>
3. 一对多映射
  • 第一种方式:collection

ClazzMapper接口

package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
public interface ClazzMapper {
 Clazz selectByCid(Integer cid);
 Clazz selectClazzAndStusByCid(Integer cid);
}

ClazzMapper.xml

<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="Student">  <!--指定集合stus中元素的类型-->
 <id property="sid" column="sid"/> 
 <result property="sname" column="sname"/>
</collection>
</resultMap>

<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c join t_student s on c.cid = s.cid where c.cid = #{cid}
</select>

ClazzMapperTest.testSelectClazzAndStusByCid

package com.powernode.mybatis.test;

import com.powernode.mybatis.mapper.ClazzMapper;
import com.powernode.mybatis.pojo.Clazz;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;

public class ClazzMapperTest {
 @Test
 public void testSelectClazzAndStusByCid() {
     ClazzMapper mapper = SqlSessionUtil.openSession().getMapper(ClazzMapper.class);
     Clazz clazz = mapper.selectClazzAndStusByCid(1001);
     System.out.println(clazz);
 }
}

  • 第二种方式
  • 修改以下三个位置即可

ClazzMapper.xml

<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--主要看这里-->
<collection property="stus"
           select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
           column="cid"/>
</resultMap>

<!--sql语句也变化了-->
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c where c.cid = #{cid}
</select>

StudentMapper接口

/**
* 根据班级编号获取所有的学生。
* @param cid
* @return
*/
List<Student> selectByCid(Integer cid);

StudentMapper.xml

<select id="selectByCid" resultType="Student">
  select * from t_student where cid = #{cid}
</select>
4. 一对多的延迟加载
  • 和多对一的延迟加载机制是一样的

十四、MyBatis 的缓存

  • 缓存的作用:通过减少IO的方式,来提高程序的执行效率。
  • mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。
  • mybatis缓存包括:
    • 一级缓存:将查询到的数据存储到SqlSession中。
    • 二级缓存:将查询到的数据存储到SqlSessionFactory中。
    • 或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。
  • 缓存只针对于DQL语句,也就是说缓存机制只对应select语句
1. 一级缓存
  • 一级缓存默认是开启的。不需要做任何配置。

  • 原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存

  • 什么情况下不走缓存?

    • 第一种:不同的SqlSession对象。
    • 第二种:查询条件变化了。
  • 一级缓存失效情况包括两种:

    • 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。sqlSession.clearCache();
    • 第二种:第一次查询和第二次查询之间,执行了增删改操作。【这个增删改和哪张表没有关系,只要有insert delete update操作,一级缓存就失效】
2. 二级缓存
  • 二级缓存的范围是SqlSessionFactory。

  • 使用二级缓存需要具备以下几个条件:

    1. <setting name="cacheEnabled" value="true"> 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。在MyBatis-config.xml文件中
    2. 在需要使用二级缓存的SqlMapper.xml文件中添加配置:<cache />
    3. 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
    4. SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
  • 二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】

  • 二级缓存的相关配置:

    • viction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
      • LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
      • FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
      • SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
      • WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
    • flushInterval:
      • 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
    • readOnly:
      • true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
      • false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
    • size:
      • 设置二级缓存中最多可存储的java对象数量。默认值1024
3. MyBatis集成EhCache
  • 集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
  • mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。
  • EhCache是Java写的。Memcache是C语言写的。所以mybatis集成EhCache较为常见。

十五、MyBatis的逆向工程

  • 所谓的逆向工程是:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
  • 逆向工程配置与生成
  • 第一步:基础环境准备

    • 新建模块:mybatis-011-generator
    • 打包方式:jar
  • 第二步,再pom中添加逆向工程模块

pom.xml

<!--定制构建过程-->
<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>
  • 第三步:配置generatorConfig.xml
    • 该文件名必须叫做:generatorConfig.xml
    • 该文件必须放在类的根路径下。

generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "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.UnmergeableXmlMappersPlugin"/>
      
        <commentGenerator>
            <!--是否去掉生成日期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/XXX"
                        userId="root"
                        password="root">
        </jdbcConnection>

        <!-- 生成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.XXX.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空白-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- 生成SQL映射文件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.XXX.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!-- 生成Mapper接口的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.XXX.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!-- 表名和对应的实体类名-->
        <table tableName="XXX" domainObjectName="XXX"/>

    </context>
</generatorConfiguration>
  • 第四步:运行插件

在这里插入图片描述

  • QBC 风格的多条件查询
    • QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
CarExample carExample = new CarExample();
carExample.createCriteria()
          .andBrandEqualTo("丰田霸道")
          .andGuidePriceGreaterThan(new BigDecimal(60.0));
carExample.or().andProduceTimeBetween("2000-10-11", "2022-10-11");
mapper.selectByExample(carExample);
sqlSession.close();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值