Mybati从持久层到大气层

Mybatis理解

什么是Mybatis?

  • Mybatis是对JDBC的封装,里面含有大量的class
  • Mybatis在三层架构中负责持久层,属于持久层框架
  • 框架其实就是对通用代码的封装,提前写好了一堆接口和类,我们可以在做项目的时候直接引入这些接口和类(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率。
  • 支持定制化 SQL、存储过程、基本映射以及高级映射
  • 避免了几乎所有的 JDBC 代码中手动设置参数以及获取结果集
  • 支持XML开发,也支持注解式开发。为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。
  • 将接口和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录

ORM是什么?

  • ORM是一种映射关系,具体表现在 在关系型数据库跟业务实体对象之间作一个映射

在这里插入图片描述

为什么要用Mybatis?JDBC不好嘛?

String sql = "select id,idCard,username,password,birth,gender,email,city,street,zipcode,phone,grade from t_user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
List<User> userList = new ArrayList<>();
while(rs.next()){
    String id = rs.getString("id");
    String idCard = rs.getString("idCard");
    String username = rs.getString("username");
    String password = rs.getString("password");
    User user = new User();
    user.setId(id);
    user.setIdCard(idCard);
    user.setUsername(username);
    user.setPassword(password);
}

以上代码有大量重复的代码,而且sql语句写死在java程序当中,一旦需要写别的sql,就要反复重复上面的代码

所以可以使用Mybatis框架

开发步骤

  1. 打包方式jar

  2. 引入依赖

    • mybatis依赖
    • mysql驱动依赖
  3. 编写mybaits核心配置文件:mybatis-config.xml

    • 这个而文件名不是必须叫做mybatis-config.xml,可以用其他名字,但一般用这个

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

    • <mappers>
      <!--指定XxxMapper.xml文件的路径,resource是从类路径下开始加载资源-->
         <mapper resource="CarMapper.xml"></mapper>
          <!--url属性,从绝对路径当中加载资源-->
          <mapper url="file:///d:xxxx.xml"></mapper>
      </mappers>
      //事务管理机制,type有两个值:JDBC,MANAGED
      //JDBC:mybatis自己用JDBC的事务方法,		       conn.seAutoCommit(false),conn.commit();
      //MANAGEN:mybatis不负责事务,事务管理交给其他容器来管理,例如:Sping 
          
      <transactionManager type="JDBC"></transactionManager>
      
    • //mybatis事务
      SqlSession sqlSession = sqlSessionFactory.openSession();
      //如果事务管理器是JDBC,底层实际上执行了:conn.setAutoCommit(false),开启事务
      System.out.println(sqlSession.getConnection().getAutoCommit());//false
      //sqlSession.commit();
      //JDBC:如果不提交,数据库数据不会更新,因为开启了事务,关闭了自动提交
      
      //表示没有提交事务,因为开启了自动提交,底层没有执行conn.setAutoCommit(false)
      //那么autoCommit就是true,每执行一次DML语句就提交一次
      //所以下面代码不建议
      SqlSession sqlSession = sqlSessionFactory.openSession(true);
      
      //如果事务管理器是MANAGEN,交给别的容器管理事务,如果没有别的容器的情况下,等于说没人管理事务,等于说没有事务,等于说不开启事务,等于说开启自动提交
      SqlSession sqlSession = sqlSessionFactory.openSession();
      
  4. 编写XxxMapper.xml文件

    • 在这个文件中编写sql语句
  5. 在mybatis-config.xml文件中指定XxxMapper.xml文件的路径:

    • <mapper resource="CarMapper.xml"/>
    • resource属性会自动从类的根路径下开始查找资源
  6. 开始编写mybatis程序

在Mybatis中负责执行sql语句的对象叫 sqlSession

sqlSession是专门用来执行sql,是一个java程序和数据库之间的一次会话

怎么获取SQLSessionFactory对象?

  • 首先需要先获取SqlSessionFactoryBuilder对象

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

  • SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession

    从xml中构建sqlSessionFactory

    mybatis中有两个重要的配置文件

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

事务机制的深度理解

//mybatis事务
SqlSession sqlSession = sqlSessionFactory.openSession();
//如果事务管理器是JDBC,底层实际上执行了:conn.setAutoCommit(false),开启事务
System.out.println(sqlSession.getConnection().getAutoCommit());//false
//sqlSession.commit();
//JDBC:如果不提交,数据库数据不会更新,因为开启了事务,关闭了自动提交

//表示没有提交事务,因为开启了自动提交,底层没有执行conn.setAutoCommit(false)
//那么autoCommit就是true,每执行一次DML语句就提交一次
//所以下面代码不建议
SqlSession sqlSession = sqlSessionFactory.openSession(true);

//如果事务管理器是MANAGEN,交给别的容器管理事务,如果没有别的容器的情况下,等于说没人管理事务,等于说没有事务,等于说不开启事务,等于说开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession();

单元测试

  • 命名规范:测试类名 + Test

  • 一个业务方法对应一个测试方式

  • 测试方法:public void testXxxx(){}

  • 测试方法的方法名:以test开始,假设测试的方法是sum,这个测试方法名:testSum

  • @Test注释非常重要,被这个注释标注的方法就是一个单元测试方法

  • public class MathService {
        public int sum(int a,int b ){
            return a+b;
        }
        public int sub(int a,int b ){
            return a-b;
        }
    }
    
  • public class MathServiceTest {
        @Test
        public void testSum(){
            MathService mathService = new MathService();
            int res = mathService.sum(1, 2);
            int expected = 3;
            Assert.assertEquals(expected,res);
        }
        @Test
        public void testSub(){
            MathService mathService = new MathService();
            int res = mathService.sub(10, 1);
            int expected = 3;
            //断言
            Assert.assertEquals(expected,res);
        }
    }
    

MyBatis日志框架

增删改查(CRUD)

在MyBatis中占位符不是?,是#{}

增删改同理

在CarMapper.xml中 
<insert id="ins">
         insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
   
</insert>

在java程序中使用map集合给sql传值

public static void main(String[] args) {
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        Map<String,Object> map = new HashMap<>();
        map.put("car_num","111");
        map.put("brand","比亚迪");
        map.put("guide_price","570");
        map.put("produce_time","2001-5-7");
        map.put("car_type","电机");
        int cout = sqlSession.insert("ins",map);
        sqlSession.commit();
    }

实际上: 底层实际上是调用了map.get()去读值

用javaBean给sql传值

public class Car {  
	private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;
    //还有getter,setter方法
}

public void testInsertCarByBean(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        Car car = new Car(null, "2121", "保时捷2", 75.1, "2021-8-7", "新能源");
        sqlSession.insert("ins",car);
        sqlSession.commit();
        sqlSession.close();
}
  • insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});

实际上:底层上其实是去找了javabean里面的get方法,通过guidePrice,carNum这些占位符,只是将G,C大写,找的是getGuidePrice,getCarNum

查询一个(selectOne)

resultType

查询结果一定会返回,跟JDBC的结果集一样,需要指定结果集对象是什么类型的(resultType)

 @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        Object car = sqlSession.selectOne("selectById", 1);
        System.out.println(car);//结果自动调用car.toString()
        sqlSession.close();
    }

结果集对象类型(resultType)指定为Car (javaBean) ,需要指定全限定类名

<select id="selectById" resultType="com.aw.mybatis.bean.Car">
        select * from t_car where id = #{id};
</select>

结果出现问题:Car{id=1, carNum=‘null’, brand=‘宝马’, guidePrice=null, produceTime=‘null’, carTyppe=‘null’}

只有id,brand有值,其他都是null
在这里插入图片描述

数据库的值是:
在这里插入图片描述

原因:列名跟属性名对不上,javaBean中的属性名是:carNum,数据库里的列名是:car_num

第一种解决办法:让列名跟属性名对应

<select id="selectById" resultType="com.aw.mybatis.bean.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>

在这里插入图片描述
在这里插入图片描述

resultMap

第二种解决办法

不用再写别名了,单独定义一个结果映射,在这个结果映射中指定数据库表的字段名和java类的属性名的对应关系

  • id:指定resultMap的唯一标识,这个id将来要在select标签中使用
  • type:用来指定bean类的类名
  • 在result标签中,如果有主键要写id作为主键,即:,可以提升检索效率
    • property是bean类的属性名
    • column是数据库字段名
 <resultMap id="carResultMap" type="Car">
        <id property="id" column="id"/>
<!--        property是bean类的属性名,column是数据库字段名-->
        <result property="carNum" column="car_num"/>
        <result property="brand" column="brand"/>
        <result property="carType" column="car_type"/>
        <result property="guidePrice" column="guide_price"/>
        <result property="produceTime" column="produce_time"/>
    </resultMap>

<select id="selectAllByResultMap" resultMap="carResultMap">
        select * from t_car
    </select>

开启驼峰自动映射

第三种解决办法

在mybatis-config.xml中

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

查询所有

<select id="selectAll" resultType="com.aw.mybatis.bean.Car">
        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
        from t_car
</select>
@Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        List<Car> cars = sqlSession.selectList("selectAll");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }

命名空间的使用(namespace)

当存在多个xml时,有时候sql的id会重复,这样mybatis不知道执行哪个sql,可以使用namespace.id,防止id冲突

<mapper namespace="aaa">
    <insert id="ins">
        insert into t_car
            (id,car_num,brand,guide_price,produce_time,car_type)
            values
            (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
    </insert>
</mapper>

<mapper namespace="org.mybatis.example.BlogMapper">
    <insert id="ins">
        insert into t_car
            (id,car_num,brand,guide_price,produce_time,car_type)
            values
            (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
    </insert>
</mapper>
@Test
public void testInsertCarByBean(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    Car car = new Car(null, "2121", "保时捷2", 75.1, "2021-8-7", "新能源");
    sqlSession.insert("aaa.ins",car);
    sqlSession.commit();
    sqlSession.close();
}

配置文件

环境(environments)

环境environment

<!--    default表示默认使用的环境-->
<!--    默认环境的意思是:当使用mybatis创建SqlSessionFactory对象的时候,如果没有指定环境的话,会使用默认环境-->
    <environments default="mybatis_demo">
<!--        其中的一个环境,连接的数据库是mybatis_demo-->
        <environment id="mybatis_demo">
  <!--type有两个值,一个是JDBC,一个是MANAGED-->          
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

<!--        mybatis的另一个环境,也就是连接的数据库是另一个数据库test0518-->
<!--        一个环境environment会对应一个sqlSessionFactory对象-->
        <environment id="test0518">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test0518"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

案例理解

数据库信息:

  • 在mybatis_demo中t_car : 字段信息: 1 100 宝马 41 2022-07-1 燃油车
  • 在test0518中user:字段信息:测试 20 男
<mapper namespace="aaa">
    <select id="selectById" resultType="java.lang.Integer">
        select
        age
        from user
        where name = #{name};
    </select>
</mapper>

<mapper namespace="org.mybatis.example.BlogMapper">
	<select id="selectById" resultType="com.aw.mybatis.bean.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>
</mapper>
@Test
public void testEnvironment() throws Exception{
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    //采用默认环境
    SqlSessionFactory factory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession = factory1.openSession();
    Object car = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectById", 1);
    System.out.println(car);//Car{id=1, carNum='100', brand='宝马520Li', guidePrice=41.0, produceTime='2022-09-1', carTyppe='燃油车'}

    //指定环境
    SqlSessionFactory factory2 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "test0518");
    SqlSession sqlSession2 = factory2.openSession();
    Object car2 = sqlSession2.selectOne("aaa.selectById", "测试");
    System.out.println(car2); //20
}

事务管理器transactionManager

<transactionManager type="JDBC"></transactionManager>

type有两个值,一个是JDBC,一个是MANAGED

mybatis提供了一个是事务管理器接口:Transaction

这个接口有两个实现类,一个是JDBC的JdbcTransaction,一个是MANAGED的ManagedTransaction

如果type=“JDBC”,就实例化JdbcTransaction

如果type=“MANAGED”,就会实例化ManagedTransaction


数据源dataSource

<dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</dataSource>

数据源作用是什么?

  • 为程序员提供Connection对象(凡是给程序员提供Connection对象的,都叫做数据源)

  • 数据源实际上是一套规范,JDK中有这套规范:javax.sql.DataSource

  • 可以自己写数据源,只要实现DataSource接口就行了

  • 常见的数据远组件有什么?(连接池)

    • 阿里巴巴德鲁伊连接池:druid
    • c3p0
    • dbcp
  • type属性用来指定数据源的类型,就是指定具体用什么方式来获取Connection对象

  • type有三个值:UNPOOLED | POOLED | JNDI

    • UNPOOLED:不使用数据库连接池技术,每次请求都创建新的Connection对象
    • POOLED:使用mybatis自己实现的数据库连接池
    • JNDI:集成其他第三方的数据库连接池
      • JNDI是一套规范,大部分web都是先了JNDI规范:Tomcat,Jetty,WebLogic,WebSphere
      • JNDI:java命名目录规范

设置(settings)

 <settings>
     <setting name="logImpl" value="slf4j"/>
     <setting name="lazyLoadingEnabled" value="true "/>开始全局懒加载
      <setting name="mapUnderscoreToCamelCase" value="true"/>驼峰映射
 </settings>

别名(typeAliases)

<!--
        起别名
        type:指定给哪个类型起别名
        alias:指定别名
-->
    <typeAliases>
        <typeAlias type="com.aw.bean.Car" alias="Car"/>
        <typeAlias type="com.aw.bean.Log" alias="Log"/>
    </typeAliases>

在xml中:

<select id="selectById" resultType="car">
<!--<select id="selectById" resultType="com.aw.mybatis.bean.Car">-->
<!--<select id="selectById" resultType="com.aw.mybatis.bean.cAr">-->
        select id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type ascarType
        from t_car
        where id=#{id}
</select>

注意!

namespace 只能用全限定名

resultType中可以写别名,且不区分大小写

alias 可以省略不写,因为会默认使用type中的类名

但是每个bean都要写一遍typeAlias很麻烦,所以引入了用包名的方式,只要在包下,所有的类都自动起别名,别名就是类简名,且不区分大小写(意思就是大小写无所谓)

<typeAliases>
    <package name="com.aw.mybatis.bean"/>
</typeAliases>

映射器(Mappers)

等同于说是注册

<mappers>
        <mapper resource="CarMapper.xml"></mapper>
        <mapper resource="LogMapper.xml"></mapper>
</mappers>

maper有三个属性:resource,url,class

  1. resource:从类的根路径下开始查找资源,
  2. url:绝对路径
  3. class:提供mapper接口的全限定接口名,需要带有包名
    • 提供:
    • mybatis会自动去:com.aw.mybatis.mapper下找CarMapper.xml文件
    • 所以使用class这种方式,需要将xml文件放到接口的同一包下
    • 可以在resources建一个一样的目录,将xml写在mapper下,这样生成之后也是在同一目录下
      在这里插入图片描述

同样的,假如有100mapper,就要手动注册100个,很麻烦,跟typeAliases一样,提供了package的方式

 <mappers>
        <package name="com.aw.mybatis.mapper"/>
</mappers>

动态代理

Mybatis采用了动态代理的机制,让程序员不用自己写Dao的实现类

但是是有要求的:

  1. 映射器中必须是全限定类名

    • 为什么?因为在底层代理机制中,通过类名+SqlId的形式获取到sql语句

    • public Account selectByName(String name) {
              SqlSession sqlSession = SqlSessionUtil.openSqlSession();
              return (Account) sqlSession.selectOne("account.selectByName", name);
      }
      
    • 如果不指定,很难获取sql,account.selectByName,因为这个是用户自己定的,比如:

    • <!--<mapper namespace="com.aw.bank.dao.AccountDao">-->
      <mapper namespace="xxx">
      
          <select id="aa" resultType="com.aw.bank.bean.Account">
              select * from bank where name=#{name}
          </select>
          <update id="bb" >
              update bank set balance=#{balance} where name=#{name}
          </update>
      </mapper>
      

使用动态代理机制,可以只通过Dao接口实现类并且创建对象

以下是实现原理:

package com.aw.bank.utils;

import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;

/**
 * @ClassName GenerateDaoProxy
 * @Author aw
 * @Date 2022/10/30 16:46
 * @Version 1.0
 * @Description 工具类,动态生成DAO的实现类
 **/
public class GenerateDaoProxy {
    public static Object generate(SqlSession sqlSession, Class doInterface) {
        //类池
        ClassPool pool = ClassPool.getDefault();
        //制造类,指定类
        //com.aw.bank.dao.AccountDao ->com.aw.bank.dao.AccountDaoImpl
        CtClass ctClass = pool.makeClass(doInterface.getName() + "Impl");
        //制作实现类的接口
        CtClass ctInterface = pool.makeInterface(doInterface.getName());
        //实现接口(添加继承关系)
        ctClass.addInterface(ctInterface);
        //实现接口的方法
        Method[] methods = doInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            //将method实现
            try {
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName() + " ");
                methodCode.append(method.getName());
                methodCode.append("(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getName() + " ");
                    methodCode.append("arg" + i);
                    if ( i !=parameterTypes.length-1){
                        methodCode.append(",");
                    }
                }
                methodCode.append("){");
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = org.apache.ibatis.session.SqlSessionUtil.openSqlSession();");

//                methodCode.append("sqlSession.");
                //需要知道是什么类型的sql语句
                String sqlId = doInterface.getName() +"." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();

                if (sqlCommandType == SqlCommandType.DELETE) {

                }
                if (sqlCommandType ==SqlCommandType.INSERT){

                }
                if (sqlCommandType == SqlCommandType.SELECT) {
                    System.out.println(method.getReturnType().getName());
                    methodCode.append("return ("+method.getReturnType().getName() +") sqlSession.selectOne(\""+sqlId+"\",arg0);");
                }
                if (sqlCommandType == SqlCommandType.UPDATE) {
                    methodCode.append("return sqlSession.update(\""+sqlId+"\",arg0);");
                }
                methodCode.append("}");
                System.out.println(methodCode);
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Object obj =null;
        try {
            Class<?> aClass = ctClass.toClass();
            obj = aClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

Mybatis中提供了一个方法可以直接达到代理功能:

private AccountDao accountDao = SqlSessionUtil.openSqlSession().getMapper(AccountDao.class);

等同于:

SqlSession sqlSession = SqlSession.openSqlSession();
XxxMapper xxxMapper = sqlSession.getMapper(XxxMapper.class)

所以!总结一下:代理机制,跟我们自己写DaoImpl实现类有什么区别?!

我们自己写的:

//bean
//实现类
//实现类的方法
public Account selectByName(String name) {
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        return (Account) sqlSession.selectOne("account.selectByName", name);
}
//new实现类
private AccountDao accountDao = new AccountDaoImpl();
//调用实现类方法
Account toAct = accountDao.selectByName(toActName);

代理机制:

//bean
//代理机制创建对象
 private AccountDao accountDao =(AccountDao)GenerateDaoProxy.generate(SqlSessionUtil.openSqlSession(),AccountDao.class);
//调用方法
Account toAct = accountDao.selectByName(toActName);

等于说我们省了一步创建类对象,以及实现类方法

我们可以直接通过代理机制创建的对象调用方法

我们自己写是一样的,只是更麻烦一些,要实现类

${}跟#{}的区别

  • #{}

  • <select id="selectByType" resultType="com.aw.mybatis.bean.Car">
        select id, car_num as carNum, brand, guide_price as guidePrice, 
        produce_time as produceTime from t_car
        where car_type=#{carType}
    </select>
    
  • 执行结果:

  • Car{id=2, carNum='46', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='null'}
    Car{id=27, carNum='46', brand='大众', guidePrice=4.0, produceTime='2022-8-5', carTyppe='null'}
    
  • ${}

  • <select id="selectByType" resultType="com.aw.mybatis.bean.Car">
        select id, car_num as carNum, brand, guide_price as guidePrice, 
        produce_time as produceTime from t_car
        where car_type=${carType}
    </select>
    
  • 执行结果:

  • org.apache.ibatis.exceptions.PersistenceException: 
    ### Error querying database.  Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'carType' in 'class java.lang.String'
    ### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'carType' in 'class java.lang.String'
    
    

#{}:底层使用PreparedStatment,特点:先进行SQL语句的编译,然后给SQL语句的占位符?传值

${}:底层使用Statement,特点:先进行SQL语句的拼接,然后在对SQL语句进行编译

使用场景:

  • 当需要传值的时候,比如:where id=“张三”,这个时候就要用#{},因为使用了占位符?,传值的时候传的是字符串,带双引号“ ”,使用#{}

  • 不需要传值的时候,

    • 比如:order by xxx desc,当中的desc需要用户指定,但是又不是字符串,不需要双引号,使用${}
    • 有些时候需要拼串的时候:tb_xxx,需要指定表,而这个xxx需要用户传过来,就用${}

记住一点!

**使用#{}的时候,sql语句会有双引号“”,使用${},sql语句没有双引号 **

注意:

  • 当使用 的时候,参数名称必须是 {}的时候,参数名称必须是 的时候,参数名称必须是{value}或者${_parameter},否则会有找不到参数的错误

  • org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'time' in 'class java.lang.String'
    
  • <select id="selectAll" resultType="com.aw.mybatis.bean.Log">
            select * from t_log${value}
    <!--         select * from t_log${time} 会找不到time参数-->
     </select>
    
  • 但是使用#{}的时候,可以随便写参数名称

  • <delete id="deleteById">
            delete
            from t_car
            where id=#{id}
        <!--where id=#{aaa} 可以-->
        <!--where id=#{bbb} 可以-->
        </delete>
    

模糊查询

  1. 使用:‘%${}%’

    •  	select
              id,
              car_num as carNum,
              brand,
              guide_price as guidePrice,
              produce_time as produceTime,
              car_type as carType
        from t_car
        where car_type like '%${value}%'
      
    • sql: where car_type like ‘%能源%’

  2. 使用concat函数

    •   select
              id,
              car_num as carNum,
              brand,
              guide_price as guidePrice,
              produce_time as produceTime,
              car_type as carType
        from t_car
        where car_type like concat('%',#{carType},'%')
      
    • sql: where car_type like concat(‘%’,?,‘%’)

mapper属性

useGeneratedKeys="true"表示使用自动生成的主键值,

keyProperty="id"表示将主键值赋值给对象的哪个属性,这个表示将主键值赋值给Car对象的id

<insert id="insertCarUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

Mybatis多参数处理

parameterType属性的作用:告诉mybatis,这个方法的参数类型是什么类型的

List<Stu> selectByNameAndSex(String name,String sex);
<select id="selectByNameAndSex" resultType="Stu">
        select * from t_stu where name=#{name} and sex=#{sex}
</select>
//报错
org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]

当有多个参数的时候,mybatis会自动创建一个Map集合,且集合以这种方式存值:

map.put("arg0",name)

map.put("arg1",sex)

map.put("param1",name)

map.put("param2",sex)

都在同一个Map集合里

<select id="selectByNameAndSex" resultType="Stu">
    select * from t_stu where name=#{arg0} and sex=#{arg1}
</select>

运行成功:

stu{id=1, name='张三', age=50, height=1.81, brith=Mon Aug 15 00:00:00 CST 2022, sex='男'}

底层发现多参数的时候,判断参数有没有@param注解,如果没有就自动创建集合并赋值arg,param

List<Stu> selectByNameAndSex2(@Param("name") String name, @Param("sex") String sex);

底层现在创建Map集合的时候就是:

map.put("name",name)

map.put("sex",sex)

底层代理过程

只做了解
在这里插入图片描述

Map返回结果

如果执行结果没有合适的java对象来接收,可以使用map来接收

Map<String,Object> selectByIdRetMap(Long id);
<select id="selectByIdRetMap" resultType="map">
        select * from t_car where id=#{id}
</select>
@Test
    public void TestSelectByIdRetMap(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<String,Object> car = mapper.selectByIdRetMap(22L);
        System.out.println(car);
    }

返回结果:

{car_num=46, id=22, guide_price=4, produce_time=2022-8-7, brand=法拉利, car_type=燃油}

以key,value的形式存在:key是数据库字段名,value是值

ListMap返回结果

如果执行结果没有合适的java对象来接收,且有多条记录,可以用ListMpa来接收

List<Map<String ,Object>> selectAllRetListMap();
<select id="selectAllRetListMap" resultType="map">
         select * from t_car
</select>
@Test
public void TestSelectAllRetListMap(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Map<String, Object>> mapList = mapper.selectAllRetListMap();
    mapList.forEach(map->{System.out.println(map);});
    sqlSession.close();
}

返回结果:

{car_num=47, id=2, guide_price=4, produce_time=2022-8-5, brand=迈凯伦, car_type=新能源}
{car_num=46, id=22, guide_price=4, produce_time=2022-8-7, brand=法拉利, car_type=燃油}
{car_num=48, id=23, guide_price=3, produce_time=2022-8-31, brand=劳斯莱斯, car_type=电气}
{car_num=49, id=24, guide_price=4, produce_time=2022-7-5, brand=劳斯莱斯幻影, car_type=电气}
{car_num=50, id=26, guide_price=4, produce_time=2025-8-5, brand=奥迪, car_type=燃油}

在这里插入图片描述

但是有个缺点,取的时候要一个一个遍历,判断是不是自己想要的才取出来,检索效率低

MapMap返回结果

如果执行结果没有合适的java对象来接收,且有多条记录,可以用MapMpa来接收

@MapKey(“id”) 需要指定大map中什么字段作为key,一般选择主键

@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetBigMap();
<select id="selectAllRetBigMap" resultType="map">
        select * from t_car
</select>
@Test
    public void TestSelectAllRetBigMap(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<Long, Map<String, Object>> longMapMap = mapper.selectAllRetBigMap();
        System.out.println(longMapMap);
        sqlSession.close();
    }

返回结果:

{

32={car_num=50, id=32, guide_price=5, produce_time=2022-8-9, brand=小鹏, car_type=电气},

33={car_num=50, id=33, guide_price=5, produce_time=2022-8-9, brand=小鹏, car_type=电气}

}

在这里插入图片描述

动态SQL

if

在sql中可以通过标签动态的指定是否拼接相应的内容

<!--    
		if标签中test是必须的,test的返回值值是bool,如果是true就拼接内容,false就不拼接
        当使用了@Param,那么test中的是@Param指定的参数名:@Param(“xx)-> test="xx....."
        如果没有使用@Param,那么test中要出现的是param1,param2.....
        如果使用了bean,那么test中出现的是bean的属性名
 -->

例如:

 List<Car> selectByBdGpCt (@Param("brand")String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByBdGpCt" resultType="Car">
    select * from t_car where 1=1
    <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>
@Test
    public void testSelectByBdGpCt(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
//        List<Car> cars = mapper.selectByBdGpCt("雷克萨斯", 3L, "新能源");
//        List<Car> cars = mapper.selectByBdGpCt("", 0, "");
        List<Car> cars = mapper.selectByBdGpCt("雷克萨斯", null, "");
        cars.forEach(car -> System.out.println(car));
    }

那么问题来了,在sql中那么多变量,一会是sql的字段,一会是bean属性,一会又是@Param指定的值,怎么区分呢?

List<Car> selectByBdGpCt(@Param("a")String brand, @Param("b") Double guidePrice, @Param("c") String carType);
<select id="selectByBdGpCt" resultType="Car">
    select * from t_car where 1=1
    <if test="a !=null and a!= ''">
        and brand like "%"#{a}"%"
    </if>
    <if test="b !=null and b !=''">
        and guide_ price >#{b}
    </if>
    <if test="c !=null and c !=''">
        and car_type =#{c}
    </if>
</select>

这样写只是为了理解区分值的含义

通过上面的例子看出:

  • 如果有@Param注解

    • test里面就用注解值,#{}接收值也是注解值,为什么?
    • 因为用了注解之后,会生成map,map的key不就是注解值吗,value是传入的参数值
  • 如果没有@Param注解

    • 如果是只有一个参数:

      • test里面要用value接收,#{}也是用value接收
    • 如果有多个参数

      • test里面用param1,param2…接收值,#{}接收值也是用param1…
  • 如果参数传入的是一个bean对象:(User user)

    • test里面用bean对象属性值接收值,#{}接收值也是用bean对象属性值

注意!

  • 如果只传入一个参数并且是String类型,并且需要用标签时,直接写参数是不行的:

        <select id="selectByCt" resultType="Car">
            select * from t_car where 1=1
            <if test="carType !=null and carType !=''">
                and car_type =#{carType}
    		</if>
        </select>
    

    原因未知

  • 解决办法:使用value

    <select id="selectByCt" resultType="Car">
          select * from t_car where 1=1
          <if test="value !=null and value !=''">
                and car_type =#{value}
    		</if>
      </select>
    

where

  • 所有条件都为空的时候,sql不会生成where语句

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

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

tirm

  • prefix: 加前缀
  • suffix: 加后缀
  • prefixOverrides: 删除前缀
  • suffixOverrides: 删除后缀
<select id="selectByBdGpCtWithTrim" resultType="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} or
            </if>
        </trim>
    </select>

结果:select * from t_car where brand like "%"?"%" and guide_price >? and car_type =?

set

判断条件里是否为true,如果为true就执行这个set,false就不执行,并且能够自动删除多余的","

用于update的set,正常更新操作是:

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

需要创建对象new xx()然后传值,如果传的值是null,那么原来有的值也会变成null,不够灵活

如果需要做到:我只修改我想修改的值,其他的值不变

<update id="updateById">
    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 !=''"> product_time=#{produceTime},</if>
        <if test="carType !=null and carType !=''"> car_type=#{carType},</if>
    </set>
    where
        id=#{id}
</update>
Car car = new Car(34L,null,"库利南",null,null,"旧能源");
int count= mapper.updateById(car);

sql:update t_car SET brand=?, car_type=? where id=? ,只修改想修改的部分

choose when otherwise

类似:if elseif,只会执行其中一条语句

<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>
@Test
public void testSelectByChoose(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectByChoose("雷克萨斯", 3.0, "新能源");
    cars.forEach(car -> System.out.println(car));
} //select * from t_car WHERE brand like "%"?"%" 

@Test
public void testSelectByChoose(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectByChoose("", null, "新能源");
    cars.forEach(car -> System.out.println(car));
}//select * from t_car WHERE car_type =? 

@Test
    public void testSelectByChoose(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByChoose("", null, "");
        cars.forEach(car -> System.out.println(car));
    } //select * from t_car WHERE car_type =?  其中?=null

从结果不难看出:

  • 如果存在多个true的,也只会执行第一个,因为在中只执行一条或者
  • 如果中没有true,一定会执行,大不了就找不到

foreach

遍历数组或者集合,获取元素

  • collection:指定数组或者集合
  • item:数组或者集合中的元素
  • separator:循 环之间的分隔符
  • open:for循环执行开始
  • close:for循环执行结束
<delete id="deleteByIds" >
        
<!--        delete from t_car where id in(-->
<!--        <foreach collection="array" item="id" separator=",">-->
<!--            #{id}-->
<!--        </foreach>-->
<!--        )-->
        delete from t_car where id in(
        <foreach collection="ids" item="id" separator=",">
            #{id}
        </foreach>
        )
    </delete>

等同于:

<delete id="deleteByIds" >
        delete from t_car where id in
        <foreach collection="ids" item="id" separator="," open="(",close=")">
            #{id}
        </foreach>
    </delete>
int deleteByIds(@Param("ids") Long[] ids);

sql跟inclued

sql:声明sql片段

iniclued:使用sql片段

<sql id="select*">
     select * from t_car
</sql>
<select id="selectByBdGpCt" resultType="Car">
    <include refid="select*"/>
     where 1=1
    <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>

目的是减少重复代码,提高代码复用率

Mybatis高级映射

在这里插入图片描述

多对一

多个学生对应一个班级

多种映射方式:常见的包括三种

  1. 一条sql语句,级联映射
  2. 一条sql语句,association
  3. 两条sql语句,分步查询(常用,优点是可复用,支持懒加载

级联映射

在resultMap中指定传出值

<resultMap id="studentResultMap" type="Stu">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <result property="clazz.cid" column="cid"/>
    <result property="clazz.cname" column="cname"/>
</resultMap>
select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_class c on s.cid=c.cid where s.sid=#{id}

sql中查询结果为:sid,sname,cid,cname

而在resultMap中指定:

  • sid值传给Type为Stu中的属性sid
  • sname值传给Type为Stu中的属性sname
  • cid值传给Type为Stu中属性为clazz对象中的cid
    • clazz是对象,对象里也有clazz自己的属性
    • Stu对应着t_stu表,Clazz对应着t_class表,而stu里包含clazz属性说明:
      • 多对一的关系,多个学生对应一个班级,一个班级对应多个学生
      • 每个学生只有一个班级,对应到java实体关系上:Stu中含有clazz属性
案例

根据id获取学生信息,同时获取学生关联的班级信息

package com.aw.mybatis.bean;

/**
 * @ClassName Stu
 * @Author aw
 * @Date 2022/11/3 23:08
 * @Version 1.0
 * @Description 多对1,Stu是多的一方,Clazz是一的一方,代表
 **/
public class Stu {
    private Integer sid;
    private String sname;
    private Clazz clazz;
	//所有属性的get,set,toString,构造方法此处忽略不写(节省位置)实际要写
    //Clazz类里面有cid,cname属性
}
public interface StuMapper {
    /*
     * @Author aW
     * @Description 根据id获取学生信息,同时获取学生关联的班级信息
     * @Date 0:03 2022/11/4
     * @Param [id] 学生id
     * @return com.aw.mybatis.bean.Stu 学生对象,但是学生对象中含有班级对象
     **/
    Stu selectById(Integer id);
}
<resultMap id="studentResultMap" type="Stu">
    <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_class c on s.cid=c.cid
    where
        s.sid=#{id}
</select>
public class StuMapperTest {
    @Test
    public void TestSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        StuMapper mapper = sqlSession.getMapper(StuMapper.class);
        Stu stu = mapper.selectById(1);
        System.out.println(stu);
        System.out.println(stu.getSid());
        System.out.println(stu.getSname());
        System.out.println(stu.getClazz().getCid());
        System.out.println(stu.getClazz().getCname());
    }
}
//输出结果
//Stu{sid=1, sname='张三', clazz=Class{cid=1001, cname='高三1班'}}
//1	张三	1001	高三1班

association

<resultMap id="studentResultMapByAssociation" type="Stu">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>

select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_class c on s.cid=c.cid where s.sid=#{id}

sql中查询结果为:sid,sname,cid,cname

而在resultMap中指定:

  • sid值传给Type为Stu中的属性sid
  • sname值传给Type为Stu中的属性sname
  • association说明clazz属性是一个对象,类似于bean对象里套bean对象
    • cid值传给Stu中属性为clazz中的属性cid
    • cname值传给Stu中属性为clazz中的属性cname
案例

根据id获取学生信息,同时获取学生关联的班级信息

Stu selectByAssociation(Integer id);
<resultMap id="studentResultMapByAssociation" type="Stu">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>

<!--        association:一个bean对象关联另一个对象
            property:bean类中的属性,
            javaType:用来指定要映射的java类型-->
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>
    
    </resultMap>
    <select id="selectByAssociation" resultMap="studentResultMapByAssociation">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_stu s left join t_class c on s.cid=c.cid
        where
            s.sid=#{id}
    </select>
@Test
    public void TestSelectByAssociation(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        StuMapper mapper = sqlSession.getMapper(StuMapper.class);
        Stu stu = mapper.selectByAssociation(1);
        System.out.println(stu);
    }
//结果:Stu{sid=1, sname='张三', clazz=Class{cid=1001, cname='高三1班'}}
  • association:一个bean对象关联另一个对象

    • property:bean类中的属性,

    • javaType:用来指定要映射的java类型

    • select:指定下一个执行的sql语句,要用类名+方法名的方式指定

    • column:指定传入下一个sql的执行参数

    • fetchType=“lazy” :开启懒加载(局部设置,只对当前的association关联的sql语句起作用)

分步查询

思路是:先查询一个语句,根据查询结果将关联的字段传给下一个sql语句,分步查询

<resultMap id="studentResultMapByStep1" type="Stu">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <association property="clazz"
                 select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
                 column="cid" />
</resultMap>
sqlSept1:  select sid,sname,cid from t_stu where sid=#{id}
sqlSept2:  select cname,cid from t_class where cid=#{cid}

sqlSept1中查询结果为:sid,sname,cid
sqlSept2的执行条件是要有#{cid}参数传进来

而在resultMap中指定:

  • sid值传给Type为Stu中的属性sid
  • sname值传给Type为Stu中的属性sname
  • association说明clazz属性是一个对象,类似于bean对象里套bean对象
    • select:指定下一个执行的sql语句,要用类名+方法名的方式指定
    • column:指定传入下一个sql的执行参数(sqlSept2的执行需要传入cid,所以这里传入的是cid)
注意!
<association property="clazz"
                 select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
                 column="cid" />
sqlSept2:  select cname,cid from t_class where cid=#{cid}

这里association的column传入的是查询结果中的字段名(有别名用别名),而sqlSept2中#{cid}只是接收参数

案例1

根据id获取学生信息,同时获取学生关联的班级信息

public interface StuMapper {Stu selectBySidStep1(Integer id);}
public interface ClazzMapper { Clazz selectByCidStep2 (Integer cid);}
<resultMap id="studentResultMapByStep1" type="Stu">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <association property="clazz"
                 select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
                 column="cid"
    />
</resultMap>
<select id="selectBySidStep1" resultMap="studentResultMapByStep1">
    select
        sid,sname,cid
    from
        t_stu
    where
        sid=#{id}
</select>
<select id="selectByCidStep2" resultType="Clazz">
    select
        cname,cid
    from
        t_class
    where
        cid=#{cid}
</select>
@Test
public void TestSelectBySidStep1(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    StuMapper mapper = sqlSession.getMapper(StuMapper.class);
    Stu stu = mapper.selectBySidStep1(5);
    System.out.println(stu);
}

执行结果:

Preparing: select sid,sname,cid from t_stu where sid=?

Parameters: 5(Integer)

Preparing: select cname,cid from t_class where cid=?

Parameters: 1002(Integer)

从结果不难看出:确实是分步执行的

分步查询的好处:

  1. 代码复用性高,把一大堆代码拆分成一段一段,每个段都可以单独使用
  2. 支持懒加载,提高性能
懒加载跟饿汉

懒加载:当需要的时候,才去加载,不需要的时候,就不加载,提高性能

饿汉:不管需不需要,都一直加载,一直有

案例2

案例1使用懒加载

<resultMap id="studentResultMapByStep1" type="Stu">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <association property="clazz"
                 select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
                 column="cid"
                 fetchType="lazy"/>
</resultMap>
@Test
    public void TestSelectBySidStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        StuMapper mapper = sqlSession.getMapper(StuMapper.class);
        Stu stu = mapper.selectBySidStep1(5);
        System.out.println(stu.getSname());
    }
//Preparing: select sid,sname,cid from t_stu where sid=?  Parameters: 5(Integer)
//龙七
 @Test
    public void TestSelectBySidStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        StuMapper mapper = sqlSession.getMapper(StuMapper.class);
        Stu stu = mapper.selectBySidStep1(5);
        System.out.println(stu);
//        System.out.println(stu.getSname());
    }
//Preparing: select sid,sname,cid from t_stu where sid=? Parameters: 5(Integer)
//Preparing: select cname,cid from t_class where cid=?  Parameters: 1002(Integer)
//Stu{sid=5, sname='龙七', clazz=Class{cid=1002, cname='高三2班'}}

一对多

一个班级对应多个学生

多种映射方式:常见的包括三种

  1. collection
  2. 分步查询(常用,优点是可复用,支持懒加载

Collection

 <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus" ofType="Stu">
            <id property="sid" column="sid"/>
            <result property="sname" column="sname"/>
        </collection>
    </resultMap>
select c.cid,c.cname,s.sid,s.sname from t_class c left join t_stu s on c.cid=s.cid where c.cid=#{cid}

sql中查询结果为:sid,sname,cid,cname

而在resultMap中指定:

  • cid值传给Type为Clazz中的属性cid
  • cname值传给Type为Clazz中的属性cname
  • sid值传给Clazz中集合名称为stus,Clazz中集合的类型为Stu
    • stus是集合对象,集合对象里有很多个类型为Stu的对象
    • Stu对应着t_stu表,Clazz对应着t_class表,而Clazz中有集合Stu说明:
      • 一对多的关系,一个班级里面有多个学生,对应到java实体关系上:Clazz里有Stu的集合
案例

根据班级id,查询班级里的学生

package com.aw.mybatis.bean;

import java.util.List;
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Stu> stus;
 //所有属性的get,set,toString,构造方法此处忽略不写(节省位置)实际要写
    //Stu类里面有cid,cname,clazz属性 
}    
public interface ClazzMapper {
    Clazz selectByCollection(Integer cid);
}
    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus" ofType="Stu">
            <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_class c left join t_stu s on c.cid=s.cid where c.cid=#{cid}
    </select>
@Test
public void TestSelectByCollection(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = mapper.selectByCollection(1001);
    System.out.println(clazz);
}

输出结果:

Clazz{

cid=1001,

cname=‘高三1班’,

stus=[Stu{sid=1, sname=‘张三’, clazz=null}, Stu{sid=2, sname=‘李四’, clazz=null}, Stu{sid=3, sname=‘王五’, clazz=null}]

}

分步查询

思路是:先查询一个语句,根据查询结果将关联的字段传给下一个sql语句,分步查询

<resultMap id="resultMapStep1" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <collection
            property="stus"
            select="com.aw.mybatis.mapper.StuMapper.selectByCidStep2"
            fetchType="lazy"
            column="cid"
    />
</resultMap>
Step1: select cid ,cname from t_class where cid = #{cid}
Step2: select sid,sname from t_stu where cid =#{id}

sqlSept1中查询结果为:sid,sname,cid
sqlSept2的执行条件是要有#{cid}参数传进来

而在resultMap中指定:

  • cid值传给Type为Clazz中的属性cid

  • cname值传给Type为Clazz中的属性cname

  • cid值传给Clazz中集合名称为stus,Clazz中集合的类型为Stu

    • stus是集合对象,集合对象里有很多个类型为Stu的对象
    • select:指定下一个执行的sql语句,要用类名+方法名的方式指定
    • column:指定将什么传入下一个sql
案例

一个班级对应多个学生

public interface StuMapper { 
    List<Stu> selectByCidStep2(Integer id);}
public interface ClazzMapper {
    Clazz selectByCidStep1(Integer cid);}
<resultMap id="resultMapStep1" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection
                property="stus"
                select="com.aw.mybatis.mapper.StuMapper.selectByCidStep2"
                fetchType="lazy"
                column="cid"
        />
</resultMap>
    <select id="selectByCidStep1" resultMap="resultMapStep1">
        select cid ,cname from t_class where cid = #{cid}
    </select>
<select id="selectByCidStep2" resultType="Stu">
        select sid,sname from t_stu where cid =#{id}
</select>
@Test
public void TestSelectByCidStep1(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = mapper.selectByCidStep1(1001);
    System.out.println(clazz);
    //Clazz{cid=1001, cname='高三1班', stus=[Stu{sid=1, sname='张三', clazz=null}, Stu{sid=2, sname='李四', clazz=null}, Stu{sid=3, sname='王五', clazz=null}]}
}

缓存机制

执行DQL(select)的时候,将查询结果放到缓存(内存)当中,如果下一次还是执行完全相同的DQL

语句,直接从缓存中拿数据,不再查数据库,不去硬盘上找数据了

目的:提高执行效率,使用减少IO的方法来提高效率

在这里插入图片描述

mybatis缓存包括:

  • 一级缓存:将查询到的数据存储到SqlSesion中
  • 二级缓存:将查询到的数据存储到的SqlSessionFactory中
  • 或者其他的第三发缓存:EhCache(java开发),Memcache(c开发)

一级缓存

一级缓存是默认开启的,不需要做任何配置

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

@Test
public void TestSelectById(){
    SqlSession sqlSession =SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car1 = mapper.selectById(2);
    System.out.println(car1);
    Car car2 = mapper.selectById(2);
    System.out.println(car2);
    sqlSession.close();
}
2022-11-05 00:14:53.453 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==>  Preparing: select * from t_car where id=? 
2022-11-05 00:14:53.473 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:14:53.487 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <==      Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}

从结果来看只执行了一次sql,但是输出了两次结果,说明走了缓存

下面测试不是同一个sqlSession对象会怎么样

@Test
public void TestSelectById() throws IOException {
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car1 = mapper.selectById(2);
    System.out.println(car1);
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
    Car car2 = mapper2.selectById(2);
    System.out.println(car2);
    sqlSession2.close();
    sqlSession.close();
}
2022-11-05 00:17:35.356 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==>  Preparing: select * from t_car where id=? 
2022-11-05 00:17:35.375 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:17:35.387 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <==      Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
2022-11-05 00:17:35.396 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==>  Preparing: select * from t_car where id=? 
2022-11-05 00:17:35.396 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:17:35.397 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <==      Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}

执行了两次sql,说明的确一级缓存就是通过SqlSession实现的

什么时候不走缓存?

  1. SqlSession不是同一个,肯定不走缓存

  2. 查询条件不一样,肯定不走缓存

什么时候一级缓存失效?

第一次DQL跟第二次DQL之间做了以下任意一件事,一级缓存就会失效

  1. 执行了SqlSession.clearCache(),手动清除缓存
  2. 执行了INSERT或者UPDATE,不管操作哪张表,都会清除一级缓存

二级缓存

二级缓存的范围是SqlSessionFactory

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

  1. 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,不需要配置,意思就是默认开启
  2. 在需要使用二级缓存的sqlMapper.xml文件中添加配置:
  3. 在使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
  4. SqlSession对象关闭或者提交之后,一级缓存中的数据才会被写到二级缓存中,此时二级缓存才可用
public class Car implements Serializable {}
<mapper namespace="com.aw.mybatis.mapper.CarMapper">
    <cache/>
    <select id="selectById2" resultType="Car">
        select * from t_car where id=#{id}
    </select>
</mapper>
@Test
public void TestSelectById2() throws IOException {
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
    CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
    Car car1 = mapper1.selectById(2);//此时car1数据存储到一级缓存当中
    System.out.println(car1);
    sqlSession1.close();//此时car1数据存储到二级缓存当中
    Car car2 = mapper2.selectById(2);//此时二级缓存中有数据,直接取数据
    System.out.println(car2);
}
2022-11-05 10:18:08.861 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==>  Preparing: select * from t_car where id=? 
2022-11-05 10:18:08.880 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 10:18:08.894 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <==      Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
2022-11-05 10:18:08.915 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23986957]
2022-11-05 10:18:08.916 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@23986957]
2022-11-05 10:18:08.917 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 597190999 to pool.
2022-11-05 10:18:08.921 [main] DEBUG com.aw.mybatis.mapper.CarMapper - Cache Hit Ratio [com.aw.mybatis.mapper.CarMapper]: 0.5
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}

什么时候二级缓存失效?

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

二级缓存的配置信息

在这里插入图片描述

  1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。

    1. LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
    2. FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
    3. SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
    4. WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
  2. flushInterval:

    1. 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
  3. readOnly:

    1. true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
    2. false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
  4. size:

    1. 设置二级缓存中最多可存储的java对象数量。默认值1024。

MyBatis集成EhCache

集成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>

第二步:在类的根路径下新建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" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>

</ehcache>

第三步:修改SqlMapper.xml文件中的<cache/>标签,添加type属性。

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

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

@Test
public void testSelectById2() throws Exception{
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(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接口名以及生成位置。
  • 连接数据库的信息。
  • 指定哪些表参与逆向工程。

实现

步骤1

在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>

步骤2:

配置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/powernode"
                        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>

步骤3:

运行插件

在这里插入图片描述

测试测序

    @Test
    public void TestSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        //查询一个
//        Car car = mapper.selectByPrimaryKey(41L);
//        System.out.println(car);
        //查询所有,根据条件查询,如果是null,就是没有条件,就查询所有
//        List<Car> cars = mapper.selectByExample(null);
//        cars.forEach(car -> System.out.println(car));
        //条件查询
        //封装条件,通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        //根据createCriteria创造查询条件
        //andBrandEqualTo brand等于(“小鹏”)
        //andCarTypeEqualTo 和carType等于(“燃油”)
        carExample.createCriteria().andBrandEqualTo("小鹏").andCarTypeEqualTo("燃油");
        //或者
        //andBrandEqualTo brand等于(“小鹏”)
        //andCarTypeEqualTo 和carType等于(“电气”)
        carExample.or().andBrandEqualTo("小鹏").andCarTypeEqualTo("电气");
        List<Car> cars1 = mapper.selectByExample(carExample);
        cars1.forEach(car -> System.out.println(car));
        sqlSession.close();
    }

运行结果:

select id, car_num, brand, guide_price, produce_time, car_type from t_car WHERE ( brand = ? and car_type = ? ) or( brand = ? and car_type = ? ) 

Mybatis分页

pageNum:页码

pageSize:每页显示的记录条数

limit计算方法:limit (pageNum-1)*pageSize , pageSize

(pageNum-1)*pageSize:每页的起始下标

PageHelper插件

使用PageHelper插件进行分页,更加的便捷。

步骤一

引入依赖

<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.1</version>
</dependency>
步骤二

在mybatis-config.xml文件中配置插件

<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
步骤三

编写java程序

public interface CarMapper {
    List<Car> selectAll();
}
<select id="selectAll" resultMap="resultMapByPage">
        select * from t_car
    </select>

关键点:

  • 在查询语句之前开启分页功能。
  • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)
public class CarMapperTest {
    @Test
    public void TestSelectByPage(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);

        PageHelper.startPage(2, 2);
        List<Car> cars = mapper.selectAll();
        PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
        System.out.println(pageInfo);
    }
}

结果:

PageInfo{
	pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8, 
	list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
	[Car{id=29, carNum='46', brand='雷克萨斯', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}, 
	Car{id=30, carNum='47', brand='雷克萨斯', guidePrice=3.0, produceTime='2022-8-5', carTyppe='新能源'}], 
	prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}

注解开发

mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。

当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。

官方是这么说的:

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

原则:简单sql可以注解。复杂sql使用xml。

Insert

public interface CarMapper {
    @Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
    int insert(Car car);
}
@Test
    public void TestInsert(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null,"55","奔驰",2.0,"2009-4-1","电气");
        int count = mapper.insert(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

Num:页码

pageSize:每页显示的记录条数

limit计算方法:limit (pageNum-1)*pageSize , pageSize

(pageNum-1)*pageSize:每页的起始下标

PageHelper插件

使用PageHelper插件进行分页,更加的便捷。

步骤一

引入依赖

<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.1</version>
</dependency>
步骤二

在mybatis-config.xml文件中配置插件

<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
步骤三

编写java程序

public interface CarMapper {
    List<Car> selectAll();
}
<select id="selectAll" resultMap="resultMapByPage">
        select * from t_car
    </select>

关键点:

  • 在查询语句之前开启分页功能。
  • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)
public class CarMapperTest {
    @Test
    public void TestSelectByPage(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);

        PageHelper.startPage(2, 2);
        List<Car> cars = mapper.selectAll();
        PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
        System.out.println(pageInfo);
    }
}

结果:

PageInfo{
	pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8, 
	list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
	[Car{id=29, carNum='46', brand='雷克萨斯', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}, 
	Car{id=30, carNum='47', brand='雷克萨斯', guidePrice=3.0, produceTime='2022-8-5', carTyppe='新能源'}], 
	prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}

注解开发

mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。

当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。

官方是这么说的:

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

原则:简单sql可以注解。复杂sql使用xml。

Insert

public interface CarMapper {
    @Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
    int insert(Car car);
}
@Test
    public void TestInsert(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null,"55","奔驰",2.0,"2009-4-1","电气");
        int count = mapper.insert(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

Select

public interface CarMapper {
     @Results({
            @Result(property = "id",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"),
    })
    @Select("select * from t_car where id=#{id}")
    Car select(Integer id);
}
@Test
    public void TestSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSqlSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.select(29);
        System.out.println(car);
    }

Update

public interface CarMapper {
    @Update("update t_car set brand=#{brand} where id=#{id}")
    int update(@Param("brand") String brand,@Param("id") Integer id);

}
@Test
public void TestUpdate(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    int count = mapper.update("马德里", 30);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

Delete

public interface CarMapper {
    @Delete("delete from t_car where id=#{id}")
    int delete(Integer id );
}
@Test
public void TestDelete(){
    SqlSession sqlSession = SqlSessionUtil.openSqlSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    int count = mapper.delete(28);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值