写点什么

MyBatis 的在使用上的注意事项及其辨析

作者:EquatorCoco
  • 2024-06-14
    福建
  • 本文字数:17829 字

    阅读完需:约 58 分钟

准备工作


数据表结构的设计,数据表名为:t_car



t_car 表中的数据信息:



pom.xml 文件当中配置相关的依赖的 jar 包如下:



<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>
<groupId>com.rainbowsea</groupId> <artifactId>mybatis-005-crud-blog</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>
<dependencies> <!-- mybatis 的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency>
<!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
<!-- 引入 logback的依赖,这个日志框架实现了slf4j 规范--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> </dependencies>
</project>
复制代码


配置 logback 的配置文件,用于打印显示,我们的日志信息,方便我们查看我们的运行过程,效果。



<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender>
<!--mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR --> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root>
</configuration>
复制代码


配置 MyBatis 的核心配置文件,



<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <!--    起别名-->    <typeAliases>        <!--  使用 <package>	还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写 -->        <package name="com.rainbowsea.mybatis.pojo"/>    </typeAliases>    <environments default="mybatis">
<environment id="mybatis"> <!-- MANAGED 没有用第三框架管理的话,都是会被提交的,没有事务上的管理了 --> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="MySQL123"/> </dataSource> </environment> </environments> <mappers> <!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致--> <mapper resource="CarMapper.xml"></mapper> </mappers></configuration>
复制代码


对照 t_car 创建的 ORM 映射的 Car 类


注意:在 MyBatis 当中对应的 ORM ,一般在框架里对应的 Bean 实体类,一定要实现该 set 和 get 方法以及无参数构造方法,无法框架无法使用反射机制,进行操作 。


建议用包装类,这样可以防止 Null 的问题,因为(简单类型 int num = null ,是不可以赋值为 null)的编译无法通过



package com.rainbowsea.mybatis.pojo;
public class Car { // 数据库表当中的字段应该和pojo类的属性一一对应 // 建议使用包装类,这样可以防止null的问题 private Long id; private String carNum; private String brand; private Double guidePrice; private String produceTime; private String carType;
public Car() { }
public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) { this.id = id; this.carNum = carNum; this.brand = brand; this.guidePrice = guidePrice; this.produceTime = produceTime; this.carType = carType; }
@Override public String toString() { return "Car{" + "id=" + id + ", carNum='" + carNum + '\'' + ", brand='" + brand + '\'' + ", guidePrice=" + guidePrice + ", produceTime='" + produceTime + '\'' + ", catType='" + carType + '\'' + '}'; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getCarNum() { return carNum; }
public void setCarNum(String carNum) { this.carNum = carNum; }
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public Double getGuidePrice() { return guidePrice; }
public void setGuidePrice(Double guidePrice) { this.guidePrice = guidePrice; }
public String getProduceTime() { return produceTime; }
public void setProduceTime(String produceTime) { this.produceTime = produceTime; }
public String getcarType() { return carType; }
public void setcarType(String catType) { this.carType = catType; }}
复制代码


对应操作实现 CRUD(增删改查)的接口(这里是:CarMapper 接口),在 MyBtis 当中 ,关于 CRUD(增删改查)操作的接口/实现类,都是 mapper 结尾的作为持久层,而在 MVC 的三层架构中,则是以 dao 为后缀作为 CRUD(增删改查)操作的接口/实现类。



package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
public interface CarMapper {

/** * 新增 Car * @param car * @return */ int insert(Car car);

/** * 根据id 删除 Car * @param id * @return */ int deleteById(Long id);

/** * 修改汽车信息 * @param car * @return */ int update(Car car);

/** * 根据id查询汽车信息 * @param id * @return */ Car selectById(Long id);

/** * 获取所有的汽车信息 * @return */ List<Car> selectAll();}
复制代码


获取 Sqlsession 对象的工具类的编写。



package com.rainbowsea.mybatis.utils;

import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
public class SqlSessionUtil { // 工具类的构造方法一般都是私有话化的 // 工具类中所有的方法都是静态的,直接类名即可调用,不需要 new 对象 // 为了防止new对象,构造方法私有化。
private SqlSessionUtil() {
}


private static SqlSessionFactory sessionFactory = null;
// 静态代码块,类加载时执行 // SqlSessionUtil 工具类在进行第一次加载的时候,解析mybatis-config.xml 文件,创建SqlSessionFactory对象。 static { // 获取到 SqlSessionFactoryBuilder 对象 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 获取到SqlSessionFactory 对象 // SQlsessionFactory对象,一个SqlSessionFactory对应一个 environment, 一个environment通常是一个数据库 try { sessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis"); } catch (IOException e) { throw new RuntimeException(e); }
}
/** * 获取会话对象 * @return SqlSession */ public static SqlSession openSession() { // 获取到 SqlSession 对象 SqlSession sqlSession = sessionFactory.openSession(); return sqlSession; }}
复制代码


#{ } 与 ${ } 的区别和使用


  • #{ } :先编译 SQL 语句,再对占位符传值,底层是 PrepareedStatement 实现,可以防止 SQL 注入,比较常用

  • ${ }:先进行 SQL 语句的拼接,然后再对 SQL 语句进行编译,底层是 Statement 实现,这种方式存在 SQL 注入现象,SQL 注入的风险,简单的说就是,直接将传入的值拼接为了 SQL 语句,然后再执行的)。只有在需要进行 SQL 语句关键字拼接的情况下才会用到。

  • 简单的说一个区别就是:#{} 传的值是带有 '' 单引号的,而 ${} 传的值是(直接就是值,没有单引号,或者是双引号,两个都没有)


检验一下。


这里我们根据汽车的 car_type 查询,为新能源汽车的。多条记录


首先这里我们先使用 #{} ,传的是带有 '' 单引号的值 ——> '新能源'




package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
/** * 封装汽车相关信息的pojo类,普通的Java类 */public interface CarMapper {
/** * 根据汽车类型获取汽车信息 * @param carType * @return */ List<Car> selectByCarType(String carType);
}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper">
<!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectByCarType" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = #{carType} </select>
</mapper>
复制代码


运行 Java 程序:




查询成功。


下面我们再实验用 ${} ,直接就是新能源的值,没有单引号,也没有双引号,就是直接就是——》新能源的值。运行结果如下:



比较上述 #{}和 ?{} 的运行结果:


{}



${}




通过执行可以清楚的看到,sql 语句中是带有 ? 的,这个 ? 就是大家在 JDBC 中所学的占位符,专门用来接收值的。


把“新能源”以 String 类型的值,传递给 ? ,加上 '' 单引号,作为字符串传入进行,执行 SQL 语句

这就是 #{},它会先进行 sql 语句的预编译,然后再给占位符传值。


而 ${} 是直接将我们的 新能源 作为值,传入给 SQL 语句的,注意 ${} 的方式是不会加单引号/双引号的,而是作为值,直接拼接到 SQL 语句当中去了,但是,在我们这个查询的 SQL 当中,新能源就是必须要为字符串才行的,不然是无法执行 SQL 语句,是无法识别出来的。



2.1 什么情况下必须使用 $


当需要进行 sql 语句关键字拼接的时候,简单的说就是当我们要使用 SQL 语句当中的关键字的时候,以及要传的值,不要单引号/双引号的值的时候,就必须使用 ${ }


需求:通过向 sql 语句中注入 asc 或 desc 关键字,来完成数据的升序或降序排列。


根据数据表中的 produce_time 日期时间进行排序。


因为这里我们使用到了 ASC / DESC 这两者都是 SQL 语句当中的关键字,所以我们需要使用 ${} 的方式。



package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
/** * 封装汽车相关信息的pojo类,普通的Java类 */public interface CarMapper {
/** * 查询所有的汽车信息,然后通过 asc 升序,desc 降序 * @param ascOrDesc * @return */ List<Car> selectAllByAscOrDesc(String ascOrDesc);

}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper">
<!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectAllByAscOrDesc" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ${ascOrDesc} </select>
</mapper>
复制代码


Java 程序运行测试:



public class TestCarMapper {
@Test public void testSelectAllByAscOrDesc() { SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); List<Car> cars = mapper.selectAllByAscOrDesc("asc"); cars.forEach(car -> { System.out.println(car); });
sqlSession.close(); }
}
复制代码


如果换成 #{} ,将 SQL 语句的关键字 asc 作为字符串的形式拼接到 SQL 语句当中是会编译失败的了。



2.1.1 拼接表名


业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022 年 8 月 1 日生成的表:t_user20220108。2000 年 1 月 1 日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为:2000 年 1 月 1 日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将表名拼接到 sql 语句当中,返回查询结果。那么大家思考一下,拼接表名到 sql 语句当中应该使用 #{} 还是 ${} 呢?

使用 #{}会是这样:select * from 't_car'

使用 ${}会是这样:select * from t_car

向 SQL 语句当中拼接表名,就需要使用 ${}现实业务当中,可能存在分表存储的数据的情况,因为一张表存的话,数据量太大了,查询效率比较低。可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高,因为扫描的数据量变小了日志表,专门存储日志信息,如果 t_long 只有一张表,这张表中每一天都会产生很多的 log,慢慢的,这个表中数据会很多,怎么解决呢可以每天生成一个新表,每张表以当天日期作为名称,例如:t_log_202209801t_log_20220902 你想知道某一天的日志信息怎么办呢?假设今天 20220901,那么直接查:t_log-20220901 的表即可。


下面是我们的 t_log_20220902 的数据表信息




对应数据表的 POJO 类设计:



package com.rainbowsea.mybatis.pojo;

public class Log { private Integer id; private String log; private String time;
public Log() { }

public Log(Integer id, String log, String time) { this.id = id; this.log = log; this.time = time; }

@Override public String toString() { return "Log{" + "id=" + id + ", log='" + log + '\'' + ", time='" + time + '\'' + '}'; }

public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getLog() { return log; }
public void setLog(String log) { this.log = log; }
public String getTime() { return time; }
public void setTime(String time) { this.time = time; }}
复制代码


对应的 LogMapper 的接口信息编写。


首先我们使用 ${} 的方式测试运行。




package com.rainbowsea.mybatis.mapper;

import com.rainbowsea.mybatis.pojo.Log;
import java.util.List;
public interface LogMapper {
/** * 根据日期查询不同的表,获取表中所有的日志 * * @param date * @return */ List<Log> selectAllByTable(String date);}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.LogMapper">
<!-- id 要是 namespace 对应接口上的方法名: --> <!-- <select id="selectAllByTable" resultType="com.rainbowsea.mybatis.pojo.Log">--> <select id="selectAllByTable" resultType="com.rainbowsea.mybatis.pojo.Log"> select * from t_log_${date}; </select>
</mapper>
复制代码



package com.rainbowsea.test;
import com.rainbowsea.mybatis.mapper.LogMapper;import com.rainbowsea.mybatis.pojo.Log;import com.rainbowsea.mybatis.utils.SqlSessionUtil;import org.apache.ibatis.session.SqlSession;import org.junit.Test;
import java.util.List;
public class LogMapperTest {
@Test public void testSelectAllByTable() { SqlSession sqlSession = SqlSessionUtil.openSession(); LogMapper mapper = sqlSession.getMapper(LogMapper.class); List<Log> logs = mapper.selectAllByTable("20220902"); logs.forEach(log -> { System.out.println(log); }); }}
复制代码


使用 ${} 拼接上的结果为:select * from t_log_20220902; 直接就是 20220902 的值,没有加单引号/双引号。


使用 #{ } 的方式是会带上 ‘’ 单引号的。



使用 #{} 拼接上的结果为:select * from t_log_‘20220902’; 加上的单引号后拼接上去,t_log_20220902 是一个数据表名的,加了单引号后,MySQL 就找不到该数据表了,编译无法通过了。


2.1.2 批量删除

业务背景:一次删除多条记录。



批量删除:一次删除多条记录批量删除的 SQL 语句有两种写法:

第一种:or: delete form t_car where id =1 or id = 2 or id = 3第二种:delete from t_car where id in(1,2,3)
复制代码


假设现在使用 in 的方式处理,前端传过来的字符串:1, 2, 3

如果使用 mybatis 处理,应该使用 #{} 还是 ${}

使用 #{} :delete from t_user where id in('1,2,3') 会加上单引号, 执行错误:1292 - Truncated incorrect DOUBLE value: '1,2,3'

使用 ${} :delete from t_user where id in(1, 2, 3),执行成功


所以我们要采用 ${} ,不加单引号的形式。




package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
/** * 封装汽车相关信息的pojo类,普通的Java类 */public interface CarMapper {
/** * 批量删除,根据id * @param ids * @return */ int deleteBath(String ids);}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper">
<!-- id 要是 namespace 对应接口上的方法名: --> <delete id="deleteBath"> delete from t_car where id in(${ids}) </delete>
</mapper>
复制代码


Java 程序测试,


注意因为我们这里是对数据表进行的修改操作的,所以需要提交一下数据给数据库。


这里我们删除 id 为(121,122,123) 三条记录。




package com.rainbowsea.test;
import com.rainbowsea.mybatis.mapper.CarMapper;import com.rainbowsea.mybatis.pojo.Car;import com.rainbowsea.mybatis.utils.SqlSessionUtil;import org.apache.ibatis.session.SqlSession;import org.junit.Test;
import java.util.List;
public class TestCarMapper {
@Test public void testDeleteBath() { SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); int count = mapper.deleteBath("121,122,123"); sqlSession.commit(); sqlSession.close();
}
}
复制代码


如果使用 #{} 是将 使用 #{} :delete from t_user where id in('121,122,123') 会加上单引号, 执行错误:1292 - Truncated incorrect DOUBLE value: '121,122,123'



2.1.3 模糊查询


需求:查询小米系列的汽车。【只要品牌 brand 中含有小米两个字的都查询出来。】


关于模糊查询有四种方案:

  1. 方案一:'%${brand}%'

  2. 方案二: concat('%','${brand}','%')

  3. 方案三: count 函数,这个是 mysql 数据库当中的一个函数,专门进行字符串拼接的 concat('%',#{brand},'%')

  4. 方案四: "%"#{brand}"%", 这种方式比较常用,也避免了 SQL 注入的问题。

方案一:'%${brand}%'方案二: concat('%','${brand}','%')方案三: count函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接的        concat('%',#{brand},'%')方案四: "%"#{brand}"%"
复制代码


四种方式:总的来说,都是为了拼接成字符串,把不是字符串的想办法拼接成字符串 。


2.1.3.1 使用 ${ }的方式


方案一:'%${brand}%'




package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
/** * 封装汽车相关信息的pojo类,普通的Java类 */public interface CarMapper {



/** * 根据汽车品牌进行模糊查询 * @param brand * @return */ List<Car> selectByBrandLike(String brand);


}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper"> <!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectByBrandLike" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like '%${brand}%' </select>
</mapper>
复制代码


Java 程序运行结果:



方案二: concat('%','${brand}','%')



<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper"> <!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectByBrandLike" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%','${brand}','%') </select></mapper>
复制代码



2.1.3.2 使用 #{ } 的方式


方案三: count 函数,这个是 mysql 数据库当中的一个函数,专门进行字符串拼接的 concat('%',#{brand},'%')



<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper"> <!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectByBrandLike" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%',#{brand},'%') </select></mapper>
复制代码



方案四: "%"#{brand}"%", 这种方式比较常用,也避免了 SQL 注入的问题。



<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper"> <!-- id 要是 namespace 对应接口上的方法名: --> <!-- 这样因为:数据表的字段名和我们定义POJO类的属性名不一致,要将二者保持一致,用 关键字 as 定义别名的方式保持一致--> <select id="selectByBrandLike" resultType="com.rainbowsea.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like "%"#{brand}"%" </select></mapper>
复制代码


测试运行:



3. typeAliases 别名定义的使用


我们来观察一下 CarMapper.xml 中的配置信息:



resultType 属性用来指定查询结果集的封装类型,这个名字太长,可以起别名吗?可以。


在 mybatis-config.xml 文件中使用 typeAliases 标签来起别名,包括两种方式:

3.1 typeAliases 的第一种方式:typeAlias



    <!--    起别名-->    <typeAliases>        <typeAlias type="com.rainbowsea.mybatis.pojo.Car" alias="Car"></typeAlias>        <typeAlias type="com.rainbowsea.mybatis.pojo.Log" alias="Log"></typeAlias>    </typeAliases>
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <!--    起别名-->    <typeAliases>        <typeAlias type="com.rainbowsea.mybatis.pojo.Car" alias="Car"></typeAlias>        <typeAlias type="com.rainbowsea.mybatis.pojo.Log" alias="Log"></typeAlias>    </typeAliases>
<environments default="mybatis">
<environment id="mybatis"> <!-- MANAGED 没有用第三框架管理的话,都是会被提交的,没有事务上的管理了 --> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="MySQL123"/> </dataSource> </environment> </environments> <mappers> <!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致--> <mapper resource="CarMapper.xml"></mapper> <mapper resource="LogMapper.xml"></mapper> </mappers></configuration>
复制代码


  • 首先要注意 typeAliases 标签的放置位置,如果不清楚的话,可以看看错误提示信息。

  • typeAliases 标签中的 typeAlias 可以写多个。

  • typeAlias:

  • type 属性:指定给哪个类起别名

    alias 属性:别名。

  • alias 属性不是必须的,如果缺省的话,type 属性指定的类型名的简类名作为别名。

    alias 是大小写不敏感的。也就是说假设 alias="Car",再用的时候,可以 CAR,也可以 car,也可以 Car,都行、


注意:<namespace = “”>接口,一定要为全限定类名(带有包名),不可以用别名机制


测试:




特别的: 省略 alias 属性之后,别名就是类的简名了,比如 :com.rainbowsea.mybatis.pojo.Car 的别名就是 Car/CAR/cAR,不区分大小写的。



    <!--    起别名-->    <!--            省略alias 属性之后,别名就是类的简名了,比如:com.rainbowsea.mybatis.pojo.Car 的别名就是 Car/CAR/cAR     不区分大小写的。-->    <typeAliases>        <typeAlias type="com.rainbowsea.mybatis.pojo.Car" ></typeAlias>        <typeAlias type="com.rainbowsea.mybatis.pojo.Log"></typeAlias>    </typeAliases>
复制代码


3.2 typeAliases 的第二种方式:package


如果一个包下的类太多,每个类都要起别名,会导致 typeAlias 标签配置较多,所以 mybatis 用提供 package 的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。


注意:<namespace = “”>接口,一定要为全限定类名(带有包名),不可以用别名机制


这种方式是最常用的。



<!--    别名 -->    <typeAliases>        <!--使用 <package>	还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写-->        <package name="com.rainbowsea.mybatis.pojo"/>    </typeAliases>
复制代码


测试:



注意:


使用 <package> 还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写

,所有的别名不区分大小写。但是:namespace 不能使用别名机制

同时需要按照一定的顺序放置,放到指定的顺序当中去


4. mappers 路径设置的使用


SQL 映射文件的配置方式包括四种:


  • resource:从类路径中加载

  • url:从指定的全限定资源路径中加载

  • class:使用映射器接口实现类的完全限定类名

  • package:将包内的映射器接口实现全部注册为映射器


4.1 mapper 标签下的 resource 属性的使用


这种方式是从类路径中加载配置文件,所以这种方式要求 SQL 映射文件必须放在 resources 目录下或其子目录下。

<mappers>  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>  <mapper resource="org/mybatis/builder/PostMapper.xml"/></mappers>
复制代码



4.2 mapper 标签下的 url 属性的使用


这种方式是一种绝对路径的方式,这种方式不要求配置文件必须放到类的路径当中,哪里都行,只要提供了一个绝对路径就行,这种方式使用极少,因为移植性太差了(并不是所以的系统都有盘符的说法的)。



**需要注意的是:要三个\\\ ,才表示两个 \\ **

<mappers>  <mapper url="file:///var/mappers/AuthorMapper.xml"/>  <mapper url="file:///var/mappers/BlogMapper.xml"/>  <mapper url="file:///var/mappers/PostMapper.xml"/></mappers>
复制代码


4.3 mapper 标签下的 class 属性的使用


Class: 这位置提供的是 mapper 接口的全限定接口名,必须带有包名(就是要一定要和对应接口的路径是一致的,一致的,一致的)


如果使用这种方式必须满足以下条件:


  • SQL 映射文件和 mapper 接口放在同一个目录下。

  • SQL 映射文件的名字也必须和 mapper 接口名一致。

        <mapper class="com.rainbowsea.mybatis.mapper.CarMapper"><mapper>        如果你class指定是:com.rainbowsea.mybatis.mapper.CarMapper        那么mybatis框架会自动去com/rainbowsea/mybatis/mapper/CarMapper/的目录下找,注意是 / 注意:也就是说,如果你采用这种方式,那么你必须保证:CarMapper.xml文件和CarMapper接口必须在同一个目录下,并且名字也是一致的
CarMapper接口——》CarMapper.xmlLogMapper接口——> LogMapper.xml
复制代码


提醒:在 IDEA 的 resources 目录下新建多重目录的话,必须是这样创建:com/rianbowsea/mybatis/mapper/不然是 com.rianbowsea.mybatis.mapper 这是建包了




保持一致的同时,名称也要是一致的



    <mappers>        <!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致-->       <mapper class="com.rainbowsea.mybatis.mapper.CarMapper"></mapper>       <mapper class="com.rainbowsea.mybatis.mapper.LogMapper"></mapper>    </mappers>
复制代码


测试运行:

运行程序:正常!!!



4.4 package 标签的使用


如果 class 较多,可以使用这种 package 的方式,但前提条件和上一种(mapper 标签下的 class 属性的使用)方式一样。


如果使用这种方式必须满足以下条件:


  • SQL 映射文件和 mapper 接口放在同一个目录下。

  • SQL 映射文件的名字也必须和 mapper 接口名一致。


这种方式是最常用的。

    <!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致-->    <package name="com.rainbowsea.mybatis.mapper"></package>
复制代码



运行测试:



5. 在 IDEA 中自定义配置文件模板


mybatis-config.xml 和 SqlMapper.xml ,logback 文件可以在 IDEA 中提前创建好模板,以后通过模板创建配置文件。




mybatis 核心配置文件的模板内容 :

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender>
<!--mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR --> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root>
</configuration>
复制代码


logback 的日志配置模板内容 :

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender>
<!--mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR --> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root>
</configuration>
复制代码


SqLMapper 的配置模板内容:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper		PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"		"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="">
<!-- id 要是 namespace 对应接口上的方法名: --> </mapper>
复制代码


6. 补充:插入数据时获取自动生成的主键的值


**前提是:主键是自动生成的。 **

业务背景:一个用户有多个角色。



插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。


插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的 id 插入到角色表的 user_id 字段上。


第一种方式:可以先插入用户数据,再写一条查询语句获取 id,然后再插入 user_id 字段。【比较麻烦】


第二种方式:mybatis 提供了一种方式更加便捷。

<!--	userGeneratedKeys = "true" 使用自动生成的主键值	keyProperty="id" 指定主键值赋值给对象的哪个属性,这个就表示将主键值给Car对象的 id属性。	注意:这个 keyProperty 指定的值,一定要和对应上的 pojo 对象类上的属性一致,不然,不行的	-->	<insert id="insertCarUserGeneratedKey" useGeneratedKeys="true" keyProperty="id" >		insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})	</insert>
复制代码


userGeneratedKeys = "true" 使用自动生成的主键值,false 则是不使用自动生成的主键值了 keyProperty="id" 指定主键值赋值给 POJO 类(ORM 映射)对象的哪个属性,这个就表示将主键值给 Car 对象的 id 属性。注意:这个 keyProperty 指定的值,一定要和对应上的 pojo 对象类上的属性一致,不然,不行的。




package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Car;
import java.util.List;
/** * 封装汽车相关信息的pojo类,普通的Java类 */public interface CarMapper {
/** * 插入 Car 信息,并且使用生成的主键值 * @param car * @return */ int insertCarUserGeneratedKey(Car car);
}
复制代码


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定要是:对应的接口的全限定类名--><mapper namespace="com.rainbowsea.mybatis.mapper.CarMapper"> <!-- userGeneratedKeys = "true" 使用自动生成的主键值 keyProperty="id" 指定主键值赋值给对象的哪个属性,这个就表示将主键值给Car对象的 id属性。 注意:这个 keyProperty 指定的值,一定要和对应上的 pojo 对象类上的属性一致,不然,不行的 --> <insert id="insertCarUserGeneratedKey" useGeneratedKeys="true" keyProperty="id"> insert into t_car values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType}) </insert></mapper>
复制代码


运行测试:




7. 总结:


  1. { } 与 ${ } 的区别和使用

- #{ } :先编译 SQL 语句,再对占位符传值,底层是 PrepareedStatement 实现,可以防止 SQL 注入,比较常用
- ${ }:先进行SQL语句的拼接,然后再对SQL语句进行编译,底层是 Statement 实现,这种方式存在 SQL注入现象,SQL注入的风险,简单的说就是,直接将传入的值拼接为了SQL语句,然后再执行的)。只有在需要进行SQL语句关键字拼接的情况下才会用到。
- 简单的说一个区别就是:#{} 传的值是带有 '' 单引号的,而 ${} 传的值是(直接就是值,没有单引号,或者是双引号,两个都没有)
  1. 什么情况下必须使用 $

拼接表名
批量删除
模糊查询
  1. typeAliases 别名定义的使用,注意:<namespace = “”>接口,一定要为全限定类名(带有包名),不可以用别名机制 ,建议采用第二种 package 方式,,同时注意:typeAliases 的正确顺序和位置,可以参考报错信息,进行修正。

<!--    别名 -->
    <typeAliases>
        <!--使用 <package>	还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写-->
        <package name="com.rainbowsea.mybatis.pojo"/>
    </typeAliases>
  1. mappers 路径设置的使用 ,建议采用 package 标签的方式,注意条件,两个条件的一致性。

- SQL映射文件和mapper接口放在同一个目录下。
- SQL映射文件的名字也必须和mapper接口名一致。
  1. 在 IDEA 中自定义配置文件模板

  2. 插入数据时获取自动生成的主键的值,**前提是:在对应数据表中的主键是自动生成的(自增的方式才行)。 **

<!--
	userGeneratedKeys = "true" 使用自动生成的主键值
	keyProperty="id" 指定主键值赋值给对象的哪个属性,这个就表示将主键值给Car对象的 id属性。
	注意:这个 keyProperty 指定的值,一定要和对应上的 pojo 对象类上的属性一致,不然,不行的
	-->
	<insert id="insertCarUserGeneratedKey" useGeneratedKeys="true" keyProperty="id" >
		insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
	</insert>


文章转载自:Rainbow-Sea

原文链接:https://www.cnblogs.com/TheMagicalRainbowSea/p/18227167

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
MyBatis 的在使用上的注意事项及其辨析_mybatis_EquatorCoco_InfoQ写作社区