Notas de estudio del código fuente de MyBatis (2) - MyBatis Advanced (Parte A)

"¡Llega la oferta, busquen amigos para que la recojan! Estoy participando en el evento de registro de reclutamiento de primavera de 2022, haga clic para ver los detalles del evento ".

1. Cree un proyecto intermedio de mybatis

Copie y cambie el nombre del proyecto mybatis-quick-start creado en MyBatis Source Code Study Notes (1) - MyBatis Overview a mybatis-intermediate.

2. Archivo de configuración MyBatis Configuration

mybatis-config.xml es el núcleo de MyBatis y afectará el comportamiento de MyBatis.

La etiqueta de configuración de MyBatis contiene las siguientes etiquetas, cada una de las cuales tiene un rol diferente

imagen.png

Uso de alias typeAliases

Agregue la configuración de alias bajo la etiqueta de configuración en mybatis-config.xml

<!--定义默认的别名,类名首字母小写-->
<typeAliases>
    <package name="com.lilith.entity" />
</typeAliases>
复制代码

En UserMapper, siempre que se use "com.lilith.entity.User", se puede usar "usuario" en su lugar

<select id="selectByPrimaryKey" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user where id = #{id}
</select>
复制代码

Ejecute el método selectByPrimaryKey en la clase de prueba

imagen.png

La etiqueta de entorno configura el entorno de desarrollo de MyBatis (DEV, TEST, PROD)

  • El elemento de entorno es el comienzo de la configuración de una fuente de datos, y la identificación del atributo es su identificador único, DEV, TEST, PROD
  • El elemento transactionManager configura las transacciones de la base de datos, donde el atributo de tipo tiene tres métodos de configuración
  • jdbc, usando jdbc para administrar transacciones;
  • gestionado, gestiona las transacciones de forma similar a un contenedor y se utiliza en fuentes de datos JNDI;
  • Personalizar, personalizar los métodos de gestión de transacciones de bases de datos;
  • El elemento dataSource configura la información de conexión de la fuente de datos. El atributo type es la configuración del método de conexión a la base de datos. Hay cuatro métodos de configuración.
    • Conexión de grupo sin conexión UNPOOLED
    • POOLED 使用连接池连接,数据库连接关闭后会放回连接池,等待下一次使用
    • JNDI 使用JNDI数据源
    • 自定义数据源

MyBatis settings标签配置

settings中的配置都是全局配置,虽然配置很多,但是常用的也就只有几个

  • cacheEnabled: 该配置会影响所有映射器(Mapper XML文件中 mapper标签)配置的缓存的全局开关,默认值为true。
  • lazyLoadingEnabled: 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联 关系中可通过设置fetchType属性来覆盖该项的开关状态,默热值为false
  • aggressiveLazyLoading: 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载; 反之,每种属性将会按需加载,默认值为true
  • multipleResultSetsEnabled:是否允许单一语句返回多结果集(需要兼容驱动),默认为true
  • userColumnLabel: 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考 相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果,默认为true
  • userGeneratedKeys:允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设 置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作,默认为fasle,当设置了主键auto increment时要开启这个配置
  • autoMappingBehavior:指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射 PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套),默认为PARTIAL
  • defaultExecutorType:配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会 重用预处理语句(prepared statements); BATCH 执行器将重用 语句并执行批量更新。默认值时SIMPLE
  • defaultStatementTimeout:设置超时时间,它决定驱动等待数据库响应的秒数
  • safeRowBoundsEnabled:允许在嵌套语句中使用分页(RowBounds),默认是false
  • mapUnderscoreToCamelCase:是否开启自动驼峰命名规则(camel case)映射,即从经典数据库 列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射,默认是false
  • localCacheScope:MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circula r references)和加速重复嵌套查询。 默认值为 SESSION,这种情 况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT, 本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会 共享数据
  • jdbcTypeForNull:当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER,jdbcType枚举最常见的是NULL,VARCHAR,OTHER。默认是OTHER
  • lazyLoadTriggerMethods:指定哪个对象的方法触发一次延迟加载,如果是方法列表用逗号隔开,默认值equals,clon e,hashCode ,toString
  • callSettersOnNulls:指定当结果集中值为 null 的时候是否调用映射对象的 setter (map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean 等)是不能设置成 null 的。
  • logPrefix:指定 MyBatis 增加到日志名称的前缀。
  • logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
  • proxyFactory:指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具
<!-- 参数设置 -->
<settings>
    <!-- 这个配置使全局的映射器启用或禁用缓存 -->
    <setting name="cacheEnabled" value="true" />
    <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载 -->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载 -->
    <setting name="aggressiveLazyLoading" value="true" />
    <!-- 允许或不允许多种结果集从一个单独的语句中返回(需要适合的驱动) -->
    <setting name="multipleResultSetsEnabled" value="true" />
    <!-- 使用列标签代替列名。不同的驱动在这方便表现不同。参考驱动文档或充分测试两种方法来决定所使用的驱动 -->
    <setting name="useColumnLabel" value="true" />
    <!-- 允许JDBC支持生成的键。需要适合的驱动。如果设置为true则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如Derby) -->
    <setting name="useGeneratedKeys" value="true" />
    <!-- 指定MyBatis如何自动映射列到字段/属性。PARTIAL只会自动映射简单,没有嵌套的结果。FULL会自动映射任意复杂的结果(嵌套的或其他情况) -->
    <setting name="autoMappingBehavior" value="PARTIAL" />
    <!--当检测出未知列(或未知属性)时,如何处理,默认情况下没有任何提示,这在测试的时候很不方便,不容易找到错误。 NONE : 不做任何处理 
        (默认值) WARNING : 警告日志形式的详细信息 FAILING : 映射失败,抛出异常和详细信息 -->
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING" />
    <!-- 配置默认的执行器。SIMPLE执行器没有什么特别之处。REUSE执行器重用预处理语句。BATCH执行器重用语句和批量更新 -->
    <setting name="defaultExecutorType" value="SIMPLE" />
    <!-- 设置超时时间,它决定驱动等待一个数据库响应的时间 -->
    <setting name="defaultStatementTimeout" value="25000" />
    <!--设置查询返回值数量,可以被查询数值覆盖 -->
    <setting name="defaultFetchSize" value="100" />
    <!-- 允许在嵌套语句中使用分页 -->
    <setting name="safeRowBoundsEnabled" value="false" />
    <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 
        的类似映射。 -->
    <setting name="mapUnderscoreToCamelCase" value="false" />
    <!--MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 
        默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 
        的不同调用将不会共享数据。 -->
    <setting name="localCacheScope" value="SESSION" />
    <!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 
        NULL、VARCHAR OTHER。 -->
    <setting name="jdbcTypeForNull" value="OTHER" />
    <!-- 指定哪个对象的方法触发一次延迟加载。 -->
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
</settings>
复制代码

mappers标签配置mapper文件

在mappers标签下配置Mapper XML文件共有四种方式

  • 用classpath类路径资源引用
<mappers>
    <mapper resource="mappers/UserMapper.xml"></mapper>
</mappers>
复制代码
  • 用类注册的方式引用
<mappers>
    <mapper resource="com.lilith.mapper.UserMapper"></mapper>
</mappers>
复制代码
  • 使用包名引入映射文件名
<mappers>
    <mapper resource="com.lilith.mapper"></mapper>
</mappers>
复制代码
  • 用映射文件的绝对路径应用
<mappers>
    <mapper resource="/Users/Practice/March/mybatis-intermediate/src/main/resources/mappers/UserMapper.xml"></mapper>
</mappers>
复制代码

推荐使用第一种方式

三、MyBatis Mapper XML 文件

mapper配置文件主要用来配置SQL语句和映射规则,mapper标签有一个namespace属性,它的属性值应该是Mapper接口的全类名,这是一个约定,这样才能形成Mapper接口与Mapper文件的映射关系,而mapper标签中又包含了以下这几个标签

  • cache – 给定命名空间的缓存配置
  • cache-ref – 其他命名空间缓存配置的引用
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
  • sql – 可被其他语句引用的可重用语句块
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

sql 标签

sql标签用来配置sql片段,针对重复次数较多的SQL片段,并使用include标签来引用配置的sql片段

<sql id="Base_Column_List">
    id,user_name,real_name,sex,mobile,email,note,position_id
</sql>

<select id="selectByPrimaryKey" resultType="user">
    SELECT
    <include refid="Base_Column_List"></include>
    FROM t_user where id = #{id}
</select>
复制代码

参数传递 #{} 和 ${}

SQL语句中获取参数值的方式有两种,一种是#{参数名},另一种是${参数名},两者的区别在于:

  • #{} 会将传入的数据当成一个字串,进行预编译也就是会对自动传入的数据加一个双引号,能很大程度上方式SQL注入
  • ${} 则是将传入的值直接显示在SQL语句中,无法防止SQL注入

${}传值出现SQL注入

在UserMapper中新增一个方法selectById

List<User> selectById(Object id);
复制代码

在UserMapper.xml中增加映射的SQL语句,这里使用${}传值

<select id="selectById" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user where id = ${id}
</select>
复制代码

在UserMapperTest中新增测试方法selectById

@Test
public void selectById() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.selectById("1 or 1 = 1");
    System.out.println("查询到的数据为:" + users);
}
复制代码

执行测试 imagen.png

根据控制台输出,该方法将所有的数据全部查询出来,这就是典型的SQL注入情况,将${}改为#{}再次执行测试

imagen.png 只查出了id为1的数据,有效的避免了SQL注入

表名、选取的列是动态的,ORDER BY和IN操作都可以使用${}来传值

UserMapper接口中新增方法selectByTablename

User selectByTablename(String tablename);
复制代码

增加selectByTablename的映射SQL语句

<select id="selectByTablename" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM ${tablename} where id = 1
</select>
复制代码

测试类中增加测试方法selectByTablename

@Test
public void selectByTablename() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user =  mapper.selectByTablename("t_user");
    System.out.println("查询到的数据为:" + user);
}
复制代码

执行测试

imagen.png

resultMap 标签使用

resultMap 是MyBatis中最重要最强大的标签,它可以让你从90%的JDBC ResultSets代码中解脱,对复杂语句进行联合映射时,会非常方便

resultMap的设计思想是简单的语句不需要明确的结果映射,复杂的语句只需要描述属性和字段的关系即可

在UserMapper.xml中增加resultMap配置,定义t_user表中的字段和User实体类属性之间的映射关系

<resultMap id="BaseResultMap" type="com.lilith.entity.User">
    <id column="id" property="id" />
    <result column="user_name" property="userName" />
    <result column="real_name" property="realName" />
    <result column="sex" property="sex" />
    <result column="mobile" property="mobile" />
    <result column="email" property="email" />
    <result column="note" property="note" />
    <result column="position_id" property="positionId" />
</resultMap>
复制代码

id和result都是将一个字段的值映射到一个简单数据类型(字符串、整型、浮点等)的属性或者字段 不同的是id表示的结果是对象的标识属性,可以提高整体性能 两个标签包含了以下属性

属性 属性表述
property Entity中的属性,如果Entity的属性匹配的字段是存在的,就会自动映射
column 表字段名
javaType 配置的Java的类
jdbcType 配置的数据库的类型
property 类型处理器,使用这个属性会覆盖默认的类型处理器,要求填写一个全类名或者是别名

如果是枚举类型,需要自定义类型转换器。常用类型不用书写jdbctype和javatype属性

resultMap的属性:

  • id:当前命名空间中的一个唯一表示,用于标识一个resultMap
  • type:类的全限定名或者是一个类的别名
  • autoMapping:如果设置这个属性,MyBatis会为这个resultMap开启或者关闭自动映射,这个属性会覆盖全局的autoMappingBehavior,默认为unset,一般不用做设置,保持默认即可

修改selectByPrimaryKey方法对应的SQL语句

<select id="selectByPrimaryKey" resultMap="BaseResultMap">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user where id = #{id}
</select>
复制代码

执行测试类中的selectByPrimaryKey方法

imagen.png

除了id和result子标签外,resultMap还有以下这些子标签:

  • constructor:用于实例化类时,注入结果到构造方法中,当类不包含无参构造方法时使用
  • association:一个复杂类型的关联,嵌套结果映射,多用于一对一查询
  • collection:复杂类型的集合,嵌套结果映射,多用于一对多查询或者多对多查询
  • discriminator:使用结果值来决定使用哪个resultMap
    • case:基于某些值得结果映射

在联合查询时association和collection标签使用较多

resultType 和 resultMap 的区别

resultType,将结果集映射到一个类上,一个类的全路径类名或者类的别名,按照类属性名和数据库字段名称是否相同进行映射,相同就将字段值赋值给属性,还可以设置开启驼峰命名

resultMap,将结果集映射到一个Map上,就是定义转换规则。

select 标签

属性 描述 备注
id 在命名空间中唯一的标识符,可以被用来引用这条语句。 如果命名空间和id组合起来不唯一,会抛出异常
parameterType 传入参数的类型;可以给出类全名,也可以给出类别名,使用别名必须是MyBatis内部定义或自定义的;基本数据类型:int、String、long、date(不知是sql.date 还是 util.date) 复杂数据类型:类 和 Map 可以选择JavaBean,Map等复杂的参数类型传递给SQL
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身;使用 resultType 或 resultMap,但不能同时使用 定义类的全路径,在允许自动匹配的情况下,结果集将通过JavaBean的规范映射; 或者定义为int,double,float等参数...,也可以使用别名,但是要符合别名规范,不能和resultMap同时使用。 它是我们常用的参数之一,比如我们总计总条数,就可以把它的值设为int
resultMap 对外部 resultMap 的命名引用。使用 resultMap 或 resultType,但不能同时使用; 它是映射集的引用,将执行强大的映射功能,我们可以使用resultType或者resultMap其 中的一个,resultMap可以给予我们自定义映射规则的机会 它是MyBatis最复杂的元素,可以配置映射 规则,级联,typeHandler等
flushCache 将它的作用是在调用SQL后,是否要求MyBatis清空之前查询的本地缓存和二级缓存 true/false,默认为false
useCache 启动二级缓存开关,是否要求MyBatis将此次结果缓存 true/false,默认为false
timeout 设置超时时间,超时之后抛出异常,秒 默认值为数据库厂商提供的JDBC驱动所设置的秒数
fetchSize 获取记录的总条数设定 默认值是数据库厂商提供的JDBC驱动所设的条数
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

SQL 语句传递多个参数

select语句传递多个参数有三种方式:

  • 使用Map传递参数,可读性差、可扩展性和可维护性差
  • 使用注解传递参数,直观明了,建议传递参数小于5个时使用
  • 使用Java Bean传递参数,当参数个数大于5个时使用
Map传参

UserMapper中新增方法,使用Map传递参数

List<User> selectByUserNameAndSex(Map<String, Object> map);
复制代码

在xml中定义对应的SQL语句

<select id="selectByUserNameAndSex" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user where user_name = #{userName} AND sex = #{sex}
</select>
复制代码

在测试类中新增测试方法,这里必须要使用Map构造查询条件

@Test
public void selectByUserNameAndSex() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 构造查询条件
    Map<String, Object> map = new HashMap<>();
    map.put("userName","loki");
    map.put("sex",1);
    List<User> userList =  mapper.selectByUserNameAndSex(map);
    System.out.println("查询到的数据为:" + userList);
}
复制代码

执行该测试方法 imagen.png 成功查询出数据

注解传参

UserMapper中新增方法selectByRealNameAndSexAndSex,这里使用@Param注解标注了参数的名字。

List<User> selectByRealNameAndSexAndSex(@Param("realName") String realName, @Param("sex") Integer sex);
复制代码

UserMapper.xml中增加对应的SQL语句

<select id="selectByRealNameAndSexAndSex" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user where real_name = #{realName} AND sex = #{sex}
</select>
复制代码

新增测试方法,调用selectByRealNameAndSexAndSex方法时传参就不需要再构造Map查询条件,直接插入对应的参数即可

@Test
public void selectByRealNameAndSexAndSex() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList =  mapper.selectByRealNameAndSexAndSex("Thor Odin", 1);
    System.out.println("查询到的数据为:" + userList);
}
复制代码

执行该测试方法 imagen.png

Java Bean 传参

当查询条件比较多时,建议将所有查询条件封装到Java Bean中,直接将Java Bean作为入参传到方法中。

User selectOneByEntity(User user);
复制代码

Agregue la instrucción SQL correspondiente a selectOneByEntity

<select id="selectOneByEntity" resultType="user">
    SELECT
    <include refid="userColumns"></include>
    FROM t_user
    WHERE id = #{id}
    AND user_name = #{userName}
    AND real_name = #{realName}
</select>
复制代码

Agregar método de prueba en UserMapperTest

@Test
public void selectOneByEntity() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(1);
    user.setUserName("stark");
    user.setRealName("Tony Stark");

    User dbUser =  mapper.selectOneByEntity(user);
    System.out.println("查询到的数据为:" + dbUser);
}
复制代码

Ejecute este método de prueba.Todos los atributos establecidos con Valor en el método de imagen.pngconsulta aparecen en las condiciones de consulta de la instrucción SQL.

Supongo que te gusta

Origin juejin.im/post/7079694208541392933
Recomendado
Clasificación