Mybatis框架课程第一天

Mybatis框架课程第一天

本日目标:

掌握基于Mapper接口的Mybatis入门使用

熟悉Mybatis使用中配置文件说明,对重要的配置文件中参数配置需要掌握

掌握Mybatis配置文件的相关(常用)配置

熟悉Mybatis动态sql语句的使用

所有开发者,编写映射文件的语句时,必须先在mysql中运行测试,然后复制到映射文件修改占位

回顾

1.基于Mapper接口中使用的设计模式

创建SqlSessionFactory时使用了构建者设计模式

生产SqlSession时使用了工厂模式

获取Mapper接口代理对象时使用了代理模式

1589248946316

2.基于Mapper接口获取代理对象执行流程

参考资源的截图:

注意:一定使用idea打开项目一起跟随点击查看

补充:

基于注解开发映射配置:(可以没有映射文件)
public interface UserMapper {
    @Select("select * from user")
    List<User>  findAll();
    @Insert("insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})")
    int addUser(User user);
    @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
    int updateUser(User user);
    @Delete("delete from user where id=#{id}")
    int deleteById(Integer id);
}
    

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

选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。(实际开发中,基于单表简单操作可以选择基于注解映射语句,复杂语句建议使用xml映射)

补充:IDEA开发Mybatis插件

1599545390637

1599545449280

1. 基于Mapper接口开发Mybatis补充

1.1 新增操作的问题扩展:

​ 获取新增用户的主键的返回值:在新增用户后,同时还要返回当前新增用户的id值,在插入数据后通过实体的get方法获取主键值

1.1.1 自增主键返回(mysql数据库)

因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长(auto_increment)的值返回。

第一种:

<!--新增后将表中指定主键列(keyColumn)的值赋值给参数实体中指定的属性(keyProperty)
Generated 生成
useGeneratedKeys="true"    使用 生成 主键 为真
-->
<!--在sql语句上添加这三个东西  useGeneratedKeys="true" keyColumn="id" keyProperty="id" 
     这个 keyColumn="id"表示数据库中的字段是id。  这个 keyProperty="id" 表示实体的属性是id 
--> 
    <insert id="addUser" parameterType="User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
	  insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>

第二种:

<insert id="saveUser" parameterType="User">
        <!-- 配置保存时获取插入的id 
        keyColumn:指定数据库列名
        keyProperty:指定将查询到的主键值设置到parameterType对象的那个属性
        order:指定语句执行顺序,值有before和after,如果不指定就按照定义顺序
        resltType:指定结果类型
        -->
      <!--   注意那个 order="AFTER",表示它是执行在插入的sql语句之后的
         那个 last_insert_id() 是mysql底层的一个方法,返回的就是我们需要的自增主键      
        -->
        <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>
1.1.2 非自增主键返回(使用mysql的uuid())

使用mysql的uuid()函数生成主键,需要修改表中id字段类型为varchar,长度设置成35位。

同样实体主键id的类型需要为String

<!--数据库没有设置自动自动的方式新增时,返回新增后的主键值-->
    <insert id="addUser4" parameterType="User_uuid">
        <!-- 使用mysql的uuid()生成主键
        执行过程:
        首先通过uuid()得到主键,然后将主键设置到user对象的id属性中
        -->
        
    <!-- 
        执行过程:
		注意到那个order="BEFORE",表示它在下面的那个sql语句执行之前执行。首先通过uuid()得到主键,然后将主键设置到user对象的id属性中(也就是把得到的这个主键,赋值给了 #{id} 括号里面的这个id)
        -->
        <selectKey keyColumn="id" keyProperty="id" resultType="java.lang.String" order="BEFORE">
            select uuid();
        </selectKey>
        insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>

如果基于java获取uuid值,那么就直接使用insert语句就好

1.1.3 非自增主键返回(使用oracle的序列)(了解)

通过oracle的序列生成主键:

<!--
   注意到那个 order="BEFORE"。
   上面的mysql用的是 select uuid(); 下面的这个Oracle用到的是 SELECT 序列名.nextval()
-->

<selectKey keyColumn="id" keyProperty="id" order="BEFORE" resultType="java.lang.String">
	SELECT 序列名.nextval()
</selectKey>
insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address})

2.1 用户模糊查询

2.1.1 在持久层接口中添加模糊查询方法
/** 
 * 根据名称模糊查询 
 * @param username 
 * @return 
 */ 
List<User> findByName(String username);
2.1.2 在用户的映射配置文件中配置
<!-- 根据名称模糊查询 --> 
<select id="findByName" resultType="User" parameterType="String"> 
	select * from user where username like #{username} 
</select>
2.1.3 加入模糊查询的测试方法
	@Test
    public void findByName() throws IOException {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.创建SqlSession工厂对象
        SqlSessionFactory factory = builder.build(in);
        //4.创建SqlSession对象
        SqlSession session = factory.openSession();
        //5.创建Dao的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        //6.执行查询方法 
        List<User> users = userDao.findByName("%王%"); 
        for(User user : users){ 
            System.out.println(user); 
        }
        //7.事务提交
        session.commit();
        //8.释放资源
        session.close();
        in.close();
    }

在控制台输出的执行SQL语句如下:

1573487437634

我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以SQL语句显示为“?”。

2.1.4 模糊查询的另一种配置方式
<!--第一步:修改SQL语句的配置,配置如下:--> 
<!-- 根据名称模糊查询 --> 
<select id="findByName" parameterType="string" resultType="User"> 		
    select * from user where username like '%${value}%' 
</select> 
<!-- 我们在上面将原来的#{}占位符,改成了${value}。一般简单值参数都使用value参数名-->
//第二步:测试,如下:
/** 
 * 测试模糊查询操作 
 */ 
@Test 
public void testFindByName(){ 
	//6.执行查询一个方法 
	List<User> users = userDao.findByName("王"); 
	for(User user : users){ 
    	System.out.println(user); 
    } 
}

在控制台输出的执行SQL语句如下:

1573487777356

可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。

#{}和${}的区别:

${}是字符串拼接,和jdbc的拼接一致,会引起SQL注入攻击

#{}是占位符,类似jdbc中使用?,可以避免注入攻击,因为如果传入字符串值会在值外出设置’ ’

建议:一般不建议使用 ${},会引起注入攻击,但是特殊的操作可以使用,也不要直接使用

使用 ${} 的话会导致 sql 注入。

什么是 SQL 注入呢?比如 select * from user where username = ${value}

value 应该是一个数值吧。然后如果对方传过来的是 zhangsan and name = tom。,如果是${}底层是拼接,把SQL语句直接写进来了。

select * from user where username = zhangsan  and name = tom
#select * from user where username = zhangsan;drop table user

如果是#{}

select * from user where username = 'zhangsan  and name = tom'
#select * from user where username = 'zhangsan;drop table user'

如果是攻击性的语句呢 zhangsan;drop table user,直接把表给删了

补充:使用模糊查询时,如果也需要解决注入攻击问题,可以使用mysql的内置函数:
concat(’%’,#{username},’%’)
<select id="findByName" parameterType="string" resultType="User"> 		
    select * from user where username like concat('%',#{username},'%') 
</select> 

2. Mybatis的参数深入(熟悉)

2.1 parameterType配置参数

2.1.1 使用说明

一般是单个参数时会使用该属性指定输入参数类型,多个参数一般可以不写(底层自动封装为Map)有框架底层提供默认的占位名标识参数(arg0,arg1,…或param1,param2,…)

使用标签的parameterType属性来设定。该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类.

2.1.2 注意事项

基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String或string。 实体类类型,目前我们只能使用全限定类名。 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。 在mybatis的官方文档的说明

1573488915814

这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。 可以参考TypeAliasRegistery.class的源码。
1573488937197

2.1.3 注册实体类别名

在mybatis-config.xml中配置

单个别名注册

<typeAliases>
    <typeAlias type="com.yaorange.entity.User" alias="User"/>
</typeAliases>

缺点:每个pojo类都要去配置。
解决方案:使用扫描包,扫描指定包下的所有类,扫描之后的别名就是类名(不区分大小写),建议使用的时候和类名一致。

<typeAliases>
    <!--type:实体类的全路径。alias:别名,通常首字母大写-->
    <!--<typeAlias type="com.yaorange.entity.User" alias="User"/>-->
    <package name="com.yaorange.entity"/>
</typeAliases>

补充:可以同时借助在实体类上使用注解@Alias(“自定义别名”),来定义实体别名,而不使用默认类名别名

1599707585903

3. Mybatis的输出结果封装

3.1 resultType配置结果类型

	resultType属性可以指定结果集的类型,它支持基本类型和实体类类型。 
	我们在前面的CRUD案例中已经对此属性进行过应用了。 
	需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名com.yaorange.entity.Users
	同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的结果集列名保持一致,否则无法实现封装。

3.1.1 特殊情况示例

1604977754913

3.1.1.1 修改实体类

实体类代码如下:(此时的实体类属性和数据库表的列名已经不一致了)

public class User implements Serializable { 
	private Integer userId; 
	private String userName; 
	private Date userBirthday; 
	private String userSex; 
	private String userAddress;
	//省略getter和setter方法
}

3.1.1.2 测试查询结果
 	@Test
    public void findAll() throws IOException {
        //6.执行查询方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
        }
    }

1573490382769
为什么userName名称会有值呢?

​ 因为:mysql在windows系统中不区分大小写!

3.1.1.3 修改映射配置 (熟悉)

使用别名查询 ,让sql语句返回的结果集字段名保持和实体属性名一致

<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="User"> 
	select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user 
</select> 

运行结果:

1573490560726

思考: 如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢? 请看下一小节。

3.2 resultMap结果类型 (掌握)

​ resultMap标签可以在查询的列名和实体类的属性名不一致时建立对应关系。从而实现封装。

​ 在select标签中使用resultMap属性指定引用替换原resultType属性即可。同时resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

3.2.1 定义resultMap
	<!--建立User实体和数据库表的对应关系
        type属性:指定实体类的全限定类名 
        id属性:给定一个唯一标识,是给查询select标签引用用的。 
     --> 
    <resultMap type="com.yaorange.entity.User" id="userMap"> 
        <id column="id" property="userId"/> 
        <result column="username" property="userName"/> 
        <result column="sex" property="userSex"/> 
        <result column="address" property="userAddress"/> 
        <result column="birthday" property="userBirthday"/> 
    </resultMap>
	<!--
	id标签:用于指定主键字段 
	result标签:用于指定非主键字段 
	column属性:用于指定数据库列名 
	property属性:用于指定实体类属性名称
	-->

3.2.2 映射配置
<!-- 配置查询所有操作 --> 
<select id="findAll" resultMap="userMap"> 
	select * from user 
</select>

3.2.3 测试结果
@Test 
public void testFindAll() { 
	List<User> users = userDao.findAll(); 
	for(User user : users) { 
		System.out.println(user); 
	} 
} 

4. 全局配置文件

4.1 配置内容

配置有顺序,顺序错误会报错

4.1.1 mybatis-config.xml中配置的内容和顺序

1599708886204

4.2 properties(属性)

在使用properties标签配置时,我们可以采用两种方式指定属性配置。

4.2.1 第一种 (不推荐使用)

不使用properties文件方式

<properties> 
	<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/> 
	<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
	<property name="jdbc.username" value="root"/> 
	<property name="jdbc.password" value="1234"/> 
</properties>

4.2.2 第二种(推荐方式)
4.2.2.1 在classpath(maven项目的java和resources都是classpath路径)下定义db.properties文件
jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/mydb 
jdbc.username=root 
jdbc.password=mysqlpass

4.2.2.2 properties标签配置
<!-- 配置连接数据库的信息 resource属性:用于指定properties配置文件的位置,要求配置文件必须在类路径下 resource="jdbcConfig.properties"-->
<properties resource="db.properties"></properties>

4.2.3 此时我们的dataSource标签就变成了引用上面的配置

使用${key名}引用属性值

<dataSource type="POOLED"> 
	<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>

4.3 settings设置(建议使用)

1573493862277

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

测试:

先修改数据库字段遵循驼峰命名:1589267743543

然后进行查询测试,看是否能返回实体数据

开启前:

1589267821262

开启后:

1589267788797

补充:log4j的使用

前提:引入log4j的依赖;并且在resources(classpath)中添加log4j.properties;然后在mybatis-config.xml那里去配置一下

	<settings>
        <!-- 开启log4j来记录日志 -->
        <setting name="logImpl" value="log4j"/>
    </settings>

4.4 typeAliases(类型别名)

在前面我们讲的Mybatis支持的默认别名,我们也可以采用自定义别名方式来开发。

4.4.1 自定义别名:

在mybatis-config.xml中配置:

<typeAliases> 
	<!-- 单个别名定义 --> 
	<typeAlias alias="user" type="com.itheima.domain.User"/> 
	<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> 
	<package name="com.yaorange.entity"/> 
	<package name="其它包"/> 
</typeAliases>

4.5 mappers(映射器)

4.5.1 mapper resource=""(可以使用)

使用相对于类路径的资源 如:类路径也就是src或者被标识为Resources Root的目录

<mapper resource="sqlmap/UserDao.xml" />

4.5.2 mapper class=" " (不建议)

使用mapper接口类路径 如:

<mapper class="com.yaorange.mapper.UserDao"/> 

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

4.5.3 package name="" (实际开发中使用的方式)

注册指定包下的所有mapper接口 如:

<package name="com.yaorange.mapper"/> 

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

实现方式:在config(或resources)定义和mapper接口一样的包名来存储映射文件

4.6 补充:typeHandlers(类型处理器)

类型处理器的作用:在执行语句时,需要将java类型数据存入数据库或将数据库数据设置给java类型属性,用于对数据库数据类型和java数据类型进行互转

如果处理一般的数据那么默认的类型处理器完全够用,但是如果存在特殊的数据时就需要自行定义类型处理器

官方默认存在类型处理器:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

自定义案例需求:

数据库字段favorites是varchar类型,存储爱好,值(‘吃,喝,玩,乐’),java实体属性是List

自定义处理器步骤:

1)定义类实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler,泛型为需要转换的java类型

2)在类上使用注解@MappedJdbcTypes(JdbcType.类型关键字):指定转换的数据库类型,使用@MappedTypes(java类型.class):指定java的数据类型

3)在自定义类中重写处理方法

4)注册当前处理器

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="自定义类型处理器全路径"/>
</typeHandlers>

参考代码:

实体:

package com.yaorange.entity;

import java.util.Date;
import java.util.List;

public class User_type {
    private Integer id;
    private String username;// 用户姓名

    private List<String> favorites;//爱好

   	//省略getter和Setter方法
}

数据库结构:

1589269524906

自定义类型处理器代码:

package com.yaorange.typeHandlers;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;


@MappedJdbcTypes(JdbcType.VARCHAR)//指定转换的数据库类型
@MappedTypes(List.class)//指定转换的java数据类型
public class ListAsVarcharTypeHandler implements TypeHandler<List<String>> {
    @Override
    //更新操作使用使用该方法给占位符转配参数
    public void setParameter(PreparedStatement preparedStatement, int i, List<String> paramters, JdbcType jdbcType) throws SQLException {
//        i:语句中占位符序号
//        paramters:参数值
//        默认底层:preparedStatement.setString(i,paramters);
        //将参数中传入的List<String>数据转换为自定义的字符串数据
        StringBuffer buffer = new StringBuffer();
        for (String str : paramters) {
            buffer.append(str).append(",");
        }
        preparedStatement.setString(i,buffer.toString());
    }

    @Override
    //查询时获取到字符串值转换实体需要的List<String>的逻辑
    //遍历结果集时,获取指定列数据有两种方式,要么通过列名获取,要么通过列索引获取
    public List<String> getResult(ResultSet resultSet, String columnName) throws SQLException {
        String favoritesString = resultSet.getString(columnName);// 获取数据库中字符串值
        if(favoritesString != null){
            return Arrays.asList(favoritesString.split(","));//将字符串转换拆分为数组并转换为List集合
        }
        return null;
    }

    @Override
    public List<String> getResult(ResultSet resultSet, int columnIndex) throws SQLException {
        String favoritesString = resultSet.getString(columnIndex);// 获取数据库中字符串值
        if(favoritesString != null){
            return Arrays.asList(favoritesString.split(","));//将字符串转换拆分为数组并转换为List集合
        }
        return null;
    }

    @Override
    public List<String> getResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        String favoritesString = callableStatement.getString(columnIndex);// 获取数据库中字符串值
        if(favoritesString != null){
            return Arrays.asList(favoritesString.split(","));//将字符串转换拆分为数组并转换为List集合
        }
        return null;
    }
}

全局配置文件配置:

<!--配置注册当前处理器-->	
<typeHandlers>
        <!--注册自定义类型处理器-->
        <typeHandler handler="com.yaorange.typeHandlers.ListAsVarcharTypeHandler"/>
    </typeHandlers>

准备Mapper接口和映射文件:

public interface UserMapper_type {
    List<User_type> getAll();

    int addUser(User_type user);
}

<?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.yaorange.mapper.UserMapper_type">
    <select id="getAll" resultType="User_type">
        select * from user_type;
    </select>

    <insert id="addUser" parameterType="User_type">
	  insert into user_type(username,favorites) values(#{username},#{favorites})
    </insert>
</mapper>

测试类代码:

package com.yaorange.typeHandlers;
import java.util.ArrayList;

import com.yaorange.entity.User_type;
import com.yaorange.mapper.UserMapper;
import com.yaorange.mapper.UserMapper_type;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class TypeHandlerTest {
    private SqlSession sqlSession;
    @Before
    public void start() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
    }
    @After
    public void end(){
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void addUserTest() throws IOException {
        UserMapper_type mapper = sqlSession.getMapper(UserMapper_type.class);

        User_type user_type = new User_type();
        user_type.setUsername("zhangsan");
        ArrayList<String> favorites = new ArrayList<>();
        favorites.add("吃");
        favorites.add("喝");
        favorites.add("玩");
        user_type.setFavorites(favorites);

        int i = mapper.addUser(user_type);
        if(i > 0){
            System.out.println("新增成功");
        }
    }
    @Test
    public void getAllTest() throws IOException {
		UserMapper_type mapper = sqlSession.getMapper(UserMapper_type.class);
        List<User_type> users = mapper.getAll();
//        System.out.println(users.get(0).getFavorites().size());
        for (User_type user : users) {
            System.out.println(user);
        }
    }
}

5. Mybatis的动态SQL语句(熟悉)

if:
适用于动态的包含where字句的一部分
if的特点是
当if的判断条件满足时,添加if标签中的字句
当if的判断条件不满足时,什么都不添加

choose:
适用于:
当判断的条件为true时,执行一个语句
当判断的条件为false时,执行另一个语句

where和set:
where的作用:
1、当where标签中的语句不为空时,会在语句之前拼接上“where”关键字
2、假如where标签中的语句是以“and”或者“or”开头,那么这个“and”或者“or”会被省略掉
set的作用:
1、假如set标签中的语句不为空,那么会在开头拼接“set”关键字
2、假如set标签中的语句是以“,”结尾,那么这个“,”会被去掉
Trim:
prefix:前缀,在赶回的字符串之前添加什么内容
suffix:后缀,在返回的字符串之后添加什么内容
prefixoverrides:当字符串以其中的内容开头时,该内容会被覆盖
suffixoverrides:当字符串以其中的内容结尾时,该内容会被覆盖
Foreach:
注意:当接口中方法的参数为数组或者集合时,mybatis会自动的把该参数封装成一个map
map的key为参数类型,首字母小写
默认:数组的key为“array”,集合的key为“list”,map的key为“map”
collection:参数在封装成的map中的key值
open:表示字符串以什么开始
close:表示字符串以什么结束
item:给集合中的每一个元素起的别名
separator:集合中元素的分隔符

Mybatis的映射文件中,前面我们的SQL都是比较简单的,有些时候业务逻辑复杂时,我们的SQL是动态变化的,此时在前面的学习中我们的SQL就不能满足要求了。

参考的官方文档,描述如下:

1573492244380

5.1 动态SQL之 if 标签

我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不为空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

5.1.1 持久层Dao接口
/** 
 * 根据用户信息,查询用户列表 
 * @param user 
 * @return 
 */ 
List<User> findByUser(User user);

5.1.2 持久层Dao映射配置
<select id="findByUser" resultType="User" parameterType="User"> 
	select * from user where 1=1 
	<if test="username!=null and username != '' "> 
		and username like concat('%',#{username},'%') 
	</if> 
	<if test="address != null"> 
		and address like '%${address}%'
	</if> 
</select> 

注意:if标签的test属性中写的是对象的属性名, 另外要注意where 1=1 的作用:当你的 字段 值为空时,保证 sql语句还能一直运行不报错

5.1.3 测试
@Test 
public void testFindByUser() { 
	User u = new User(); 
	u.setUsername("王"); 
	u.setAddress("四川"); 
	//6.执行操作 
	List<User> users = userDao.findByUser(u); 
	for(User user : users) { 
		System.out.println(user); 
	} 
}

5.2 动态SQL之 choose 标签

choose有点类似于Java中的switch,常常配合when和otherwise一起来使用。我们来看一个简单的例子:

<select id="getUser2" resultMap="u">
        SELECT * FROM user2 WHERE 1=1
        <choose>
            <when test="id!=null">
                AND id=#{id}
            </when>
            <when test="address!=null">
                AND address=#{address}
            </when>
            <when test="username!=null">
                AND user_name LIKE concat('%',#{username},'%')
            </when>
            <otherwise>
                AND 10>id
            </otherwise>
        </choose>
    </select>

在查询条件中,如果用户传来了id,那么我就查询该id的数据,如果用户传来了address,那么我就我们添加address的查询条件,如果用户传来了username,那么我就添加username的查询条件,最后如果用户任何一个查询条件都没有添加进来,那么默认查询条件就是查询id小于10的所有数据。

5.3 Mybatis中简化编写的SQL片段 (掌握)

Sql中可将重复的sql提取出来,使用时用标签引用即可,最终达到sql重用的目的。

5.3.1 定义代码片段
<!-- 抽取重复的语句代码片段 --> 
<sql id="defaultSql"> 
	select * from user 
</sql>

5.3.2 引用代码片段
<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="user"> 
	<include refid="defaultSql"/> 
</select> 
<!-- 根据id查询 --> 
<select id="findById" resultType="UsEr" parameterType="int">
    <include refid="defaultSql"/>
	where id = #{uid}
</select>

实际开发中一般使用标签定义字段列表

5.4 动态SQL之where标签

为了简化上面where 1=1的条件拼装,我们可以采用where标签来简化开发。

5.4.1 持久层Dao映射配置
	<sql id="defaultSql">
        select * from user
    </sql>	
	<!-- 根据用户信息查询 --> 
	<select id="findByUser" resultType="User" parameterType="User">
        <include refid="defaultSql"/>
        <where>
            <if test="username!=null and username != '' ">
                and username like #{username}
            </if>
            <if test="address != null">
                and address like #{address}
            </if>
        </where>
    </select>

注意:使用标签,其中包含的标签的语句都建议添加上and,mybatis底层会自动根据情况删除and,但是不会添加and(因为不一定是and,后面也可能是or。它不知道是添加and还是or,所以不能添加,但是可以删除)

5.5 动态标签之set标签

set是我们在更新表的时候使用的元素,通过set元素,我们可以逐字段的修改一条数据,如下:

<update id="update">
        UPDATE user2
        <set>
            <if test="username!=null">
                user_name=#{username},
            </if>
            <if test="password!=null">
                password=#{password}
            </if>
        </set>
        WHERE id=#{id}
    </update>

在set元素中,如果遇到了逗号,系统会自动将之去除。

5.6 动态标签之bind标签

使用bind元素我们可以预先定义一些变量,然后在查询语句中使用,如下:

	<select id="getUserByName" resultMap="u">
        <bind name="un" value="username+'%'"></bind>
            SELECT* FROM user2 WHERE user_name LIKE #{un}
    </select>

5.7 动态标签之foreach标签(熟悉)

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句或批量新增的时候)

传入参数说明:

如果传入参数是List,set,Array,Map,Properties,实际使用时外部标签一般都不写parameterType属性就好,默认全部使用map存储,效率会高一些

子标签的表达式:

数组类型的参数传入时:子标签表达式中,使用代表参数的名称为array(foreach标签collection=“”或if标签的test)(底层:map.put(“array”,我们传入的数组值对象))

Map类型的参数传入时:首先在关联方法中参数前面使用的注解@Param自定义参数别名,子标签中的表达式使用注解中定义的别名

@Param("users")   -->foreach标签 collection=“users”或if标签的test=“users......

List类型的参数传入时:子标签表达式中,使用代表参数的名称为list(foreach标签collection=“”或if标签的test)

多个参数传入时:底层会进行特殊处理,按照参数顺序依次以指定名称作为key**(arg0,arg1…param1,param2…),**以实参列表值为value,如:map.put(“arg0”,id值)或map.put(“param1”,id值)

实际开发中:对于接口方法存在多个参数或集合参数时,一般都是在mapper接口的方法参数前使用@Param(“参数别名”),底层使用map.put(“注解指定别名”,标识参数值)

5.7.1 需求

传入多个id查询用户信息,用下边两个sql实现:

SELECT * FROM USERS WHERE id =10 OR id =89 OR id=16

SELECT * FROM USERS WHERE id IN (10,89,16)

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。 这样我们将如何进行参数的传递?

5.7.2 持久层Dao接口
	/**
     * 根据id集合查询用户
     */
    List<User> findUserByArray(Integer[] ids);
    List<User> findUserByList(List<Integer> ids);
    //使用注解自定义参数别名
    List<User> findUserByMap(@Param("ids") Map<String,Integer> map);

    User findUserByIdAndUsername(Integer id,String userame);
    //使用注解自定义别名
    User findUserByIdAndUsername1(@Param("userId") Integer id,@Param("username") String userame);

5.7.3 持久层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.yaorange.mapper.UserMapper_sql">
    <!--定义公共字段标签-->
    <sql id="allColumn">
        id,username,birthday,sex,address
    </sql>

    <!--foreach:遍历参数传入的数组,以指定open开头,以指定的close结尾,以指定separator分隔数据,以指定item名称为数据占位别名-->
    <select id="findUserByArray" resultType="User">
        select <include refid="allColumn"/> from user
        <where>
            <if test="array != null and array.length > 0">
                <foreach collection="array" open="id in (" close=")" separator="," item="userID">
                    #{userID}
                </foreach>
            </if>
        </where>
    </select>
    <select id="findUserByList" resultType="User">
        select <include refid="allColumn"/> from user
        <where>
            <if test="list != null and list.size() > 0">
                <foreach collection="list" open="id in (" close=")" separator="," item="userID">
                    #{userID}
                </foreach>
            </if>
        </where>
    </select>

    <select id="findUserByMap" resultType="User">
        select <include refid="allColumn"/> from user
        <where>
            <if test="ids != null and ids.size() > 0">
                <foreach collection="ids" open="id in (" close=")" separator="," index="key" item="value">
                    #{value}
                </foreach>
            </if>
        </where>
    </select>

    <select id="findUserByIdAndUsername" resultType="User">
        select <include refid="allColumn"/> from user
        <where>
            <if test="arg0 != null and arg0 != ''">
                and id=#{arg0}
            </if>
            <if test="arg1 != null and arg1 != ''">
                and username=#{arg1}
            </if>
        </where>
    </select>

    <select id="findUserByIdAndUsername1" parameterType="map" resultType="User">
        select <include refid="allColumn"/> from user
        <where>
            <if test="userId != null and userId != ''">
                and id=#{userId}
            </if>
            <if test="username != null and username != ''">
                and username=#{username}
            </if>
        </where>
    </select>

</mapper>

1573497516163

5.7.4 编写测试方法
package com.yaorange;

import com.yaorange.entity.User;
import com.yaorange.entity.User_other;
import com.yaorange.entity.User_uuid;
import com.yaorange.mapper.UserMapper;
import com.yaorange.mapper.UserMapper_sql;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class SqlTest {
    private SqlSession sqlSession;
    @Before
    public void start() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
    }
    
    @After
    public void end(){
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void findByUserTest() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        User user = new User();
//        user.setId(41);
        user.setUsername("王五");
        List<User> users = mapper.findByUser(user);
        for (User u : users) {
            System.out.println(u);
        }
    }

    @Test
    public void findByUser1Test() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        User user = new User();
        user.setId(41);
//        user.setUsername("王五");
        List<User> users = mapper.findByUser1(user);
        for (User u : users) {
            System.out.println(u);
        }
    }

    @Test
    public void findUserByArrayTest() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        Integer[] ids = {41,43,49};
        List<User> users = mapper.findUserByArray(ids);
        for (User u : users) {
            System.out.println(u);
        }
    }

    @Test
    public void findUserByListTest() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        Integer[] ids = {41,43};
        List<User> users = mapper.findUserByList(Arrays.asList(ids));
        for (User u : users) {
            System.out.println(u);
        }
    }

    @Test
    public void findUserByMapTest() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        Map<String, Integer> map = new HashMap<>();
        map.put("zhangsan",41);
        map.put("lisi",49);
        List<User> users = mapper.findUserByMap(map);
        for (User u : users) {
            System.out.println(u);
        }
    }

    @Test
    public void findUserByIdAndUsernameTest() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
//        User user = mapper.findUserByIdAndUsername(41,null);
//        User user = mapper.findUserByIdAndUsername(null,"王五");
        User user = mapper.findUserByIdAndUsername(43,"王五");
        System.out.println(user);
    }

    @Test
    public void findUserByIdAndUsername1Test() {
        UserMapper_sql mapper = sqlSession.getMapper(UserMapper_sql.class);
        User user = mapper.findUserByIdAndUsername1(49,null);
//        User user = mapper.findUserByIdAndUsername1(null,"王五");
//        User user = mapper.findUserByIdAndUsername1(43,"王五");
        System.out.println(user);
    }

}


补充:使用多个参数的接口方法定义和语句中使用(掌握)

第一种:基于Mybatis底层默认参数命名[arg0,arg1,…]或[param1,param2,…]

User findUserByUsernameAndPassword(String username,String password);

<select id="findUserByUsernameAndPassword" resultType="User">
        select * from user where username=#{arg0} and password=#{arg1};
</select>
或:
<select id="findUserByUsernameAndPassword" resultType="com.yaorange.entity.User">
        select * from user where username=#{param1} and password=#{param2};
    </select>

第二种:使用Map存储多个参数

使用Map存储多个参数值,在语句中直接使用Map中的key标识占位别名(类似于使用一个特殊的对象,map的key就等同于对象的属性名)

User findByMap(Map<String,Object> params);
<select id="findByMap" resultType="User">
    select <include refid="allColumns"/> from user where id=#{id} and name=#{name}
</select>

测试代码:

		Map<String, Object> map = new HashMap<String, Object>();
        map.put("id",41);
        map.put("name","张三");
        List<User> users = mapper.findByMap(map);
        for (User user : users) {
            System.out.println(user);
        }

运行的语句:

1599725795232

第三种:使用注解标识参数,给参数定义别名(常用方式)

User findUser(@Param("username") String val1,@Param("pass") String val2);
<select id="findUserByUsernameAndPassword" resultType="com.yaorange.entity.User">
       select <include refid="allColumns"/> from user where username=#{username} and password=#{pass};
 </select>

面试题

1.MyBatis的好处是什么

  • MyBatis把sql语句从Java源程序中独立出来, 放在单独的XML文件中编写,给程序的维护带来了很大便利。 (我们以前直接用原生JDBC操作数据库是怎样?面试题)

  • MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象,大大简化了Java数据库编程的重复工作。

  • 因为MyBatis需要程序员自己去编写sql语句,程序员可以结合数据库本身的特点灵活控制sql语句,因此能够实现比hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。

2.MyBatis核心处理类

SqlSession:用于直接执行语句,或者返回mapper接口的代理对象,用以调用接口方法操作语句

img

参考链接:https://thinkwon.blog.csdn.net/article/details/101293216

1、SqlSessionFactoryBuilder

        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建SqlSessionFactory的构建者对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
       //3.使用构建者创建工厂对象SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in);

每一个MyBatis的应用程序的入口是SqlSessionFactoryBuilder。

它的作用是通过XML配置文件创建Configuration对象(当然也可以在程序中自行创建),然后通过build方法创建SqlSessionFactory对象。没有必要每次访问Mybatis就创建一次SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。

mybatis-config.xml中的配置,最后把xml解析成Configuration这个类。

SqlSessionFactoryBuilder根据传入的数据流(XML)生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。

2、SqlSessionFactory

SqlSessionFactory对象的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问Mybatis就创建一次SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory对象一个必要的属性是Configuration对象,它是保存Mybatis全局配置的一个配置对象,通常由SqlSessionFactoryBuilder从XML配置文件创建。

       //4.使用SqlSessionFactory生产SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

3、SqlSession

SqlSession对象的主要功能是完成一次数据库的访问和结果的映射,它类似于数据库的session概念,由于不是线程安全的,所以SqlSession对象的作用域需限制方法内。SqlSession的默认实现类是DefaultSqlSession,它有两个必须配置的属性:Configuration和Executor。Configuration前文已经描述这里不再多说。SqlSession对数据库的操作都是通过Executor来完成的。

SqlSession :默认创建DefaultSqlSession 并且开启一级缓存,创建执行器 、赋值。

SqlSession有一个重要的方法getMapper,顾名思义,这个方式是用来获取Mapper对象的

什么是Mapper对象?根据Mybatis的官方手册,应用程序除了要初始并启动Mybatis之外,还需要定义一些接口,接口里定义访问数据库的方法,存放接口的包路径下需要放置同名的XML配置文件。

SqlSession的getMapper方法是联系应用程序和Mybatis纽带,应用程序访问getMapper时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。

4、Executor

Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。

5、StatementHandler

StatementHandler是真正访问数据库的地方,并调用ResultSetHandler处理查询结果。

6、ResultSetHandler

处理查询结果。

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

​ 查询数据的时候,发现查不到userName的信息,
​ User{id=‘2’, userName=‘null’, sex=0, birthday=‘1993-09-05’}
原因:数据库的字段名是user_name,POJO中的属性名字是userName。两端不一致,造成mybatis无法填充对应的字段信息。

解决方案1:在sql语句中使用as别名:
<select id="queryUserById" resultType="com.pojo.User">
   select
    tuser.id as id,
    tuser.user_name as userName,
    tuser.birthday as birthday,
    tuser.sex as sex
    from
    tb_user tuser
    where tuser.id = #{id};
</select>

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

一定对照查询语句的结果集定义

	<resultMap id="userMap" type="User_Other">
        <!--说明数据库表主键列关联的实体类主键属性
			column,表示数据库中的字段。property,表示实体中的字段
		-->
        <id column="id" property="userId"/>
        <!--配置非主键列-->
        <result column="username" property="userName"/>
        <result column="birthday" property="userBirthday"/>
        <result column="sex" property="userSex"/>
        <result column="address" property="userAddress"/>
    </resultMap>

解决方案3:参考驼峰匹配 — mybatis-config.xml 的时候
<!--Underscore,表示“下划线,强调”。CamelCase,“驼峰式命名法”。Camel,“骆驼”-->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

**4.#{}${}**的区别是什么?

#{}是预编译处理,${}是字符串替换

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

Mybatis在处理 时 , 就 是 把 {}时, 就是把 {}直接替换成变量的值。 底层通过字符串拼接

使用#{}可以有效的防止SQL注入,提高系统安全性。

#{}和${}的区别:

${}是字符串拼接,和jdbc的拼接一致,会引起SQL注入攻击

#{}是占位符,类似jdbc中使用?,可以避免注入攻击,因为如果传入字符串值会在值外出设置’ ’

建议:一般不建议使用 ${},会引起注入攻击,但是特殊的操作可以使用,也不要直接使用

使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力。比如 select * from user where username = v a l u e 。 v a l u e 应 该 是 一 个 数 值 吧 。 然 后 如 果 对 方 传 过 来 的 是 z h a n g s a n a n d n a m e = t o m 。 如 果 是 {value} 。 value 应该是一个数值吧。然后如果对方传过来的是 zhangsan and name = tom。如果是 valuevaluezhangsanandname=tom{}底层是拼接,把SQL语句直接写进来了。

select * from user where username = zhangsan  and name = tom
#select * from user where username = zhangsan;drop table user

如果是#{}

select * from user where username = 'zhangsan  and name = tom'
#select * from user where username = 'zhangsan;drop table user'

如果是攻击性的语句呢 zhangsan;drop table user,直接把表给删了

${}

select * from user where username = zhangsan;drop table user

如果是#{}

select * from user where username = 'zhangsan;drop table user'
补充:使用模糊查询时,如果也需要解决注入攻击问题,可以使用mysql的内置函数:
concat(’%’,#{username},’%’)
<select id="findByName" parameterType="string" resultType="User"> 		
    select * from user where username like concat('%',#{username},'%') 
</select> 

5.MyBatis和ORM的区别(Object Relational Mapping)

Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道只要像平时操作对象一样操作它就可以了

mybatis属于半orm,因为sql语句需要自己写。

与其他比较标准的 ORM 框架(比如 Hibernate )不同, mybatis 并没有将 java 对象与数据库关联起来,而是将 java 方法与 sql 语句关联起来,mybatis 允许用户充分利用数据库的各种功能,例如存储、视图、各种复杂的查询以及某些数据库的专有特性。

自己写 sql 语句的好处是,可以根据自己的需求,写出最优的 sql 语句。灵活性高。但是,由于是自己写 sql 语句,导致平台可移植性不高。MySQL 语句和 Oracle 语句不同

6.MyBatis实现分页功能

1) 原始方法,使用 limit,需要自己处理分页逻辑:

对于 mysql 数据库可以使用 limit ,如:

select * from table limit 5; --返回前5行

select * from table limit 0,5; --同上,返回前5行

select * from table limit 5,10; --返回6-15行

对于 oracle 数据库可以使用 rownum ,如:

–如:从表Sys_option(主键为sys_id)中从第10条记录开始检索20条记录,语句如下

SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2

Where t2.R >= 10

2) 使用PageHelper插件,这是目前比较常见的方法:

由于篇幅太长详细过程看原文:www.cnblogs.com/digdeep/p/4608933.html

7.MyBatis接口绑定的几种方式

接口绑定有两种方式

1、使用注解,在接口的方法上面添加@Select@Update等注解,里面写上对应的SQL语句进行SQL语句的绑定。

2、通过映射文件xml方式进行绑定,指定xml映射文件中的namespace对应的接口的全路径名

什么时候用注解绑定?什么时候用xml绑定?

当SQL语句比较简单的时候,使用注解绑定就可以了,当SQL语句比较复杂的话,使用xml方式绑定,一般用xml方式绑定比较多

8.MyBatis映射文件中A标签引用B标签,如果B标签在A的后面定义,可以吗?

虽然 Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,Mybatis 都可以正确识别。

原理:

Mybatis 解析 A 标签时,发现引用了 B 标签,未解析到 B 标签,此时会把 A 标签标记为未解析状态

继续解析下面内容,把剩下解析完之后,再解析标记为未解析的标签

此时已解析到 B 标签,此时再解析A标签时,B标签已经存在,A 标签也就顺利解析完成。

9. MyBatis不同映射文件中的id是否可以重复?

可以重复,但是需要映射文件的namespace不同

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复。

原因就是 namespace+id 是作为 Map<String, MapperStatement>的 key使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。

有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。

image-20210317140736229

10.MyBatis缓存机制

缓存机制减轻数据库压力,提高数据库性能

mybatis的缓存分为两级:一级缓存、二级缓存

一级缓存:

一级缓存为 sqlsesson 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

具体流程:

第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来

第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

注意:

1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 SqlSession 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异

2、当一个 SqlSession 结束后那么他里面的一级缓存也就不存在了, mybatis 默认是开启一级缓存,不需要配置

3、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是, SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的

二级缓存:

二级缓存是mapper 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存

开启二级缓存步骤:

1、mybatis-config.xml 配置全局变量开启二级缓存

<settings>    
    <setting name="cacheEnabled" value="true"/><!-- 默认是false:关闭二级缓存 -->
<settings>

2、在userMapper.xml中配置

<!-- 当前mapper下所有语句开启二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

这里配置了一个 LRU算法 缓存,并每隔60秒刷新,最大存储512个对象,而返回的对象是只读的

若想禁用当前select语句的二级缓存,添加 useCache="false"修改如下:

<select id="getCountByName" parameterType="string" resultType="int" useCache="false">
</select>

具体流程:

1.当一个sqlseesion执行了一次select 后,在关闭此session 的时候,会将查询结果缓存到二级缓存

2.当另一个sqlsession执行select 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能

注意:

1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异

2、mybatis 的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSessionHashMap 存储缓存数据时,是使用 [namespace:sql:参数]作为 key ,查询返回的语句作为 value 保存的。

11.简述 Mybatis 的动态 SQL,列出常用的 6 个标签及作用

动态 SQL 是 MyBatis 的强大特性之一 基于功能强大的 OGNL 表达式。
动态 SQL 主要是来解决查询条件不确定的情况,在程序运行期间,根据提交的条件动态的完成查询

常用的标签:
: 进行条件的判断
:在判断后的 SQL 语句前面添加 WHERE 关键字,并处理 SQL 语句开始位置的AND 或者 OR 的问题
:可以在 SQL 语句前后进行添加指定字符 或者去掉指定字符.
: 主要用于修改操作时出现的逗号问题
:类似于 java 中的 switch 语句。在所有的条件中选择其一
:迭代操作

12.Mybatis 结果集的映射方式有几种,并分别解释每种映射方式如何使用。

自动映射 ,通过 resultType 来指定要映射的类型即可。
自定义映射 ,通过 resultMap 来完成具体的映射规则,指定将结果集中的哪个列映射到对象的哪个属性。

13.简述 Mybatis 提供的两级缓存,以及缓存的查找顺序

(1)MyBatis 的缓存分为一级缓存和 二级缓存。一级缓存是 SqlSession 级别的缓存,默认开启。

二级缓存是 NameSpace 级别(Mapper)的缓存,多个 SqlSession 可以共享,使用时需要进行配置开启。

(2)缓存的查找顺序:二级缓存 => 一级缓存 => 数据库

14.简述如何在 myBatis 中的增删改操作获取到对数据库的影响条数

直接在 Mapper 接口的方法中声明返回值即可

15.解释 MyBatis 中 @Param 注解的作用

通过该注解来指定 Mybatis 底层在处理参数时封装 Map 使用的key,方便在 SQL 映射文件中取参数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值