> 文章列表 > MyBatis(九)MyBatis小技巧

MyBatis(九)MyBatis小技巧

MyBatis(九)MyBatis小技巧

一、#{}和${}

#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。

${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。

需求:根据car_type查询汽车

模块名:mybatis-005-antic

1、使用#{}

第1步、创建新模块:

第2步、pom.xml文件引入依赖(mybatis依赖、mysql依赖、junit依赖、logback依赖)

<?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.powernode</groupId><artifactId>mybatis-006-antic</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><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><!--junit依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--logback依赖--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.11</version></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties></project>

第3步、根目录(resources)下创建(CarMapper.xml、jdbc.properties、logback.xml、mybatis-config.xml)

 ①、CarMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = #{carType}</select>
</mapper>

②、jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:1997/powernode
jdbc.username=root
jdbc.password=123456

③、logback.xml

<?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} [%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-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><properties resource="jdbc.properties"/><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><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></environment></environments><mappers><!--一定要注意这里的路径哦!!!--><mapper resource="CarMapper.xml"/></mappers>
</configuration>

第4步、编写接口、pojo类、工具类

①、Carmapper

package com.powernode.mybatis.mapper;import com.powernode.mybatis.pojo.Car;import java.util.List;/* @author wuw* @since 2023-04-07 11:04:43*/
public interface CarMapper {/* 根据car_num获取Car* @param carType* @return*/List<Car> selectByCarType(String carType);
}

②、Car

package com.powernode.mybatis.pojo;/* @author wuw* @since 2023-04-07 11:53:16*/
public class Car {private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;// 构造方法// set get方法// toString方法
}

③、SqlSessionUtil

package com.powernode.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;/* @author wuw* @since 2023-04-04 09:42:28*/
public class SqlSessionUtil {private static SqlSessionFactory sqlSessionFactory;/* 类加载时初始化sqlSessionFactory对象*/static {try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (Exception e) {e.printStackTrace();}}private static ThreadLocal<SqlSession> local = new ThreadLocal<>();/* 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。 @return 新的会话对象*/public static SqlSession openSession() {SqlSession sqlSession = local.get();if (sqlSession == null) {sqlSession = sqlSessionFactory.openSession();local.set(sqlSession);}return sqlSession;}/* 关闭SqlSession对象* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();}local.remove();}
}

第5步、编写测试类

import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;import java.util.List;/* @author wuw* @since 2023-04-07 11:54:23*/
public class CarMapperTest {@Testpublic void testSelectByCarType(){CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectByCarType("燃油车");cars.forEach(car -> System.out.println(car));}
}

第6步、运行

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

把“燃油车”以String类型的值,传递给 ?

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

 2、使用${}

同样的需求,我们使用${}来完成

CarMapper.xml文件修改如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhere<!--car_type = #{carType}-->car_type = ${carType}</select>
</mapper>

再次运行测试程序:

出现以上错误,因为${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为燃油车是一个字符串,所以在sql语句中应该添加单引号。

修改一下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhere<!--car_type = #{carType}-->car_type = '${carType}'</select>
</mapper>

再次运行程序:

通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式

原则:能用 #{} 就不用 ${}

 3、什么情况下必须使用${}

当需要进行sql语句关键字拼接的时候。必须使用${}

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

  • 先使用#{}尝试:

第一步、CarMapper接口:

/* 查询所有的Car* @param ascOrDesc asc或desc* @return*/
List<Car> selectAll(String ascOrDesc);

第二步、CarMapper.xml文件:

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder by carNum #{key}
</select>

第三步、测试程序:

@Testpublic void testSelectAll(){CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectAll("desc");cars.forEach(car -> System.out.println(car));}

报以下错误:

报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:

select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum 'desc'

desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}

 第4步、使用${} 改造、修改CarMapper.xml

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_car<!--order by carNum #{key}-->order by carNum ${key}
</select>

第5步、再次执行测试程序

 4、拼接表名

业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如: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

CarMapper.xml:

<select id="selectAllByTableName" resultType="car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom${tableName}
</select>

CarMapper接口:

/* 根据表名查询所有的Car* @param tableName* @return*/
List<Car> selectAllByTableName(String tableName);

CarMapperTest.testSelectAllByTableName:

@Test
public void testSelectAllByTableName(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectAllByTableName("t_car");cars.forEach(car -> System.out.println(car));
}

执行结果:

 5、批量删除

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

如果使用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)

第1步、接口CarMapper接口:

    /* 根据id批量删除* @param ids* @return*/int deleteBatch(String ids);
}

第2步、CarMapper.xml

<delete id="deleteBatch">delete from t_car where id in(${ids})
</delete>

第3步、测试类

@Test
public void testDeleteBatch(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);int count = mapper.deleteBatch("1,2");System.out.println("删除了几条记录:" + count);SqlSessionUtil.openSession().commit();
}

第4步、运行,update了两条数据

 6、模糊查询

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

①、使用${}

第1步、CarMapper接口

/* 根据品牌进行模糊查询* @param likeBrank* @return*/
List<Car> selectLikeByBrand(String likeBrank);

第2步、CarMapper.xml

<select id="selectLikeByBrand" resultType="Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like '%${brand}%'
</select>

第3步、编写测试类

@Test
public void testSelectLikeByBrand(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectLikeByBrand("比亚迪");cars.forEach(car -> System.out.println(car));
}

第4步:运行

 二、typeAliases

首先观察一下CarMapper.xml中的配置信息:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder by carNum ${key}</select><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = '${carType}'</select>
</mapper>

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

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

第一种方式:typeAlias

mybatis-config.xml文件:

<typeAliases><typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
</typeAliases>

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

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

typeAlias:

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

        alias属性:别名。

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

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

第二种方式:package

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

mybatis-config.xml文件:

<typeAliases><package name="com.powernode.mybatis.pojo"/>
</typeAliases>

测试:

在mybatis-config中配置

 CarMapper中映射一下

 运行测试代码

三、mappers

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

  • resource:从类路径中加载
  • url:从指定的全限定资源路径中加载
  • class:使用映射器接口实现类的完全限定类名
  • package:将包内的映射器接口实现全部注册为映射器

1、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>

2、url

这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。

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

3、class(使用的少,移植性太差)

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

  • SQL映射文件和mapper接口放在同一个目录下。
  • SQL映射文件的名字也必须和mapper接口名一致。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers><mapper class="com.powernode.mybatis.Carmapper"/><mapper class="com.powernode.mybatis.LogMapper"/>
</mappers>

将CarMapper.xml文件移动到和mapper接口同一个目录下:

  • 在resources目录下新建:com/powernode/mybatis/mapper【这里千万要注意:不能这样新建 com.powernode.mybatis.dao
  • 将CarMapper.xml文件移动到mapper目录下
  • 修改mybatis-config.xml文件

4、package

如果class较多,可以使用这种package的方式,但前提条件和上一种方式一样。

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers><package name="com.powernode.mybatis.mapper"/>
</mappers>

四、idea配置文件模板

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

五、插入数据时获取自动生成的主键

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

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

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

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

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

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

 第1步:CarMapper

/* 获取自动生成的主键* @param car*/
void insertUseGeneratedKeys(Car car);

第2步:CarMapper.xml

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

第3步:CarMapperTest测试

@Testpublic void testInsertUseGeneratedKeys(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);Car car = new Car();car.setCarNum("5262");car.setBrand("BYD汉");car.setGuidePrice(30.3);car.setProduceTime("2020-10-11");car.setCarType("新能源");mapper.insertUseGeneratedKeys(car);SqlSessionUtil.openSession().commit();System.out.println("获取的id为:"+" "+car.getId());}

第4步:运行