欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > MyBatis小技巧与MyBatis参数处理

MyBatis小技巧与MyBatis参数处理

2025/6/23 5:57:59 来源:https://blog.csdn.net/m0_73941339/article/details/147014779  浏览:    关键词:MyBatis小技巧与MyBatis参数处理

一、MyBatis小技巧

1 #{}和${}

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

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

需求:根据car_type查询汽车

模块名:mybatis-005-antic

⑴.使用#{}

依赖
<?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>org.example1</groupId><artifactId>mybatis-006antic</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></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><!--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></project>

接口CarMapper
package org.example1.mapper;import org.example1.pojo.Car;import java.util.List;public interface CarMapper {List<Car> selectByCarType(String carType);//根据汽车类型获取汽车信息}
Car类
package org.example1.pojo;/*** 封装汽车相关信息的pojo类。普通的java类。*/
public class Car {// 数据库表当中的字段应该和pojo类的属性一一对应。// 建议使用包装类,这样可以防止null的问题。private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;@Overridepublic String toString() {return "Car{" +"id=" + id +", carNum='" + carNum + '\'' +", brand='" + brand + '\'' +", guidePrice=" + guidePrice +", produceTime='" + produceTime + '\'' +", carType='" + carType + '\'' +'}';}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCarNum() {return carNum;}/*public String getXyz() {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 carType) {this.carType = carType;}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;}public Car() {}
}
SqlSessionUtil类
package org.example1.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;/*** MyBatis工具类*/
public class SqlSessionUtil {private SqlSessionUtil(){}private static SqlSessionFactory sqlSessionFactory;static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}// 全局的,服务器级别的,一个服务器当中定义一个即可。// 为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession。private static ThreadLocal<SqlSession> local = new ThreadLocal<>();/*** 获取会话对象。* @return 会话对象*/public static SqlSession openSession(){SqlSession sqlSession = local.get();if (sqlSession == null) {sqlSession = sqlSessionFactory.openSession();// 将sqlSession对象绑定到当前线程上。local.set(sqlSession);}return sqlSession;}/*** 关闭SqlSession对象(从当前线程中移除SqlSession对象。)* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();// 注意移除SqlSession对象和当前线程的绑定关系。// 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程。local.remove();}}}
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="org.example1.mapper.CarMapper"><select id="selectByCarType" resultType="org.example1.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = ${carType}/* car_type = '${carType}'*/</select></mapper>
jdbc.properties
logback.xml
mybatis-config.xml
test
    @Testpublic  void testSelectByCarType(){SqlSession sqlSession = SqlSessionUtil.openSession();// mapper实际上就是daoImpl对象.// 底层不但为CarMapper接口生成了字节码,并且还new实现类对象了。CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByCarType("新能源");// 遍历cars.forEach(car -> System.out.println(car));sqlSession.close();}

执行结果:

通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来接收值的。 把“燃油车”以String类型的值,传递给 ? 这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值

⑵.使用${}

同样的需求,我们使用${}来完成 CarMapper.xml文件修改如下:

再次运行测试程序:

出现异常了,这是为什么呢?看看生成的sql语句:

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

再执行测试程序:

通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。 原则:能用 #{} 就不用 ${}

⑶.什么情况下必须使用${}

当需要进行sql语句关键字拼接的时候。必须使用${} 需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。

  • 先使用#{}尝试:

CarMapper接口:

package org.example1.mapper;import org.example1.pojo.Car;import java.util.List;public interface CarMapper {/*** 查询所有的汽车信息。然后通过asc升序,desc降序。* @param ascOrDesc* @return*/List<Car> selectAllByAscOrDesc(String ascOrDesc);List<Car> selectByCarType(String carType);//根据汽车类型获取汽车信息}

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="org.example1.mapper.CarMapper"><select id="selectAllByAscOrDesc" resultType="org.example1.pojo.car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder byproduce_time ${ascOrDesc}</select></mapper>

测试程序

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

报错的原因是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语句关键字拼接的时候,必须使用${}

  • 使用${} 改造

再次执行测试程序:

#{}和${}的区别:#{}: 底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值。可以避免SQL注入的风险。${}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险。优先使用#{},这是原则。避免SQL注入的风险。
如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。

⑷.向SQL语句当中拼接表名,就需要使用${}

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

业务背景:

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

<?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="org.example1.mapper.CarMapper"><select id="selectAllByTableName" resultType="org.example1.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom${tableName}</select></mapper>
/*** 根据表名查询所有的Car* @param tableName* @return*/
List<Car> selectAllByTableName(String tableName);
@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));
}

执行结果:

⑸.批量删除

批量删除:一次删除多条记录。批量删除的SQL语句有两种写法:第一种or:delete from t_car where id=1 or id=2 or id=3;第二种int:delete from t_car where id in(1,2,3);应该采用${}的方式:delete from t_car where id in(${ids});

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

对应的sql语句:

  • delete from t_user where id = 1 or id = 2 or id = 3;

  • delete from t_user 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 org.example1.mapper;import org.example1.pojo.Car;import java.util.List;public interface CarMapper {int deleteBatch(String ids);}
<delete id="deleteBatch">delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);int count = mapper.deleteBatch("1,2,3");System.out.println("删除了几条记录:" + count);SqlSessionUtil.openSession().commit();
}

执行结果:

⑹.模糊查询

模糊查询:like需求:根据汽车品牌进行模糊查询select * from t_car where brand like '%奔驰%';select * from t_car where brand like '%比亚迪%';第一种方案:'%${brand}%'第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接concat('%',#{brand},'%')第三种方案:比较鸡肋了。可以不算。concat('%','${brand}','%')第四种方案:"%"#{brand}"%"

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

第一种方案: '%${brand}%'
①使用${}
package org.example1.mapper;import org.example1.pojo.Car;import java.util.List;public interface CarMapper {List<Car> selectLikeByBrand(String likeBrank);}
<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>
@Test
public void testSelectLikeByBrand(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectLikeByBrand("奔驰");cars.forEach(car -> System.out.println(car));
}

执行结果:

②使用#{}

第二种方案:concat函数
<?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="org.example1.mapper.CarMapper"><select id="selectLikeByBrand" resultType="org.example1.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like concat('%',#{brand},'%')</select></mapper>
①使用${}

②使用#{}

第三种方案:比较鸡肋了。可以不算。 concat('%','${brand}','%')

第四种方案:   "%"#{brand}"%"
<?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="org.example1.mapper.CarMapper"><select id="selectLikeByBrand" resultType="org.example1.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like "%"#{brand}"%"</select></mapper>

2.mybatis-config.xml文件中的typeAliases标签

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

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

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

⑴.第一种方式:typeAlias

 <typeAliases><typeAlias type="org.example1.pojo.Car" alias="Car"/></typeAliases>

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

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

  • typeAlias:

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

    • alias属性:别名。

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

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

    alias属性是可以省略的。有默认的别名。<!--省略alias之后,别名就是类的简名,比如:org.example1.pojo.Car的别名就是Car/car/cAR/cAr,不缺分大小写。 -->

⑵.第二种方式:package

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

package也可以配置多个的。

3. mybatis-config.xml文件中的mappers标签。

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

  • resource:从类路径中加载

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

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

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

⑴.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>

⑵.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>

⑶.class

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

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

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

<mapper class="全限定接口名,带有包名"/>
class: 这个位置提供的是mapper接口的全限定接口名,必须带有包名的。思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?<mapper class="org.example1.mapper.CarMapper"/>如果你class指定是:org.example1.mapper.CarMapper 那么mybatis框架会自动去org/example1/mapper目录下查找CarMapper.xml文件。注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。CarMapper接口-> CarMapper.xmlLogMapper接口-> LogMapper.xml

⑷.package

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

4.idea配置文件模板

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

5.插入数据时获取自动生成的主键

前提是:主键是自动生成的。 业务背景:一个用户有多个角色。

插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。 插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上。

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

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

CarMapper接口

package org.example1.mapper;import org.example1.pojo.Car;import java.util.List;public interface CarMapper {/*** 插入Car信息,并且使用生成的主键值。* @param car* @return*/int insertCarUseGeneratedKeys(Car car);}

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="org.example1.mapper.CarMapper"><!--useGeneratedKeys="true" 使用自动生成的主键值。keyProperty="id" 指定主键值赋值给对象的哪个属性。这个就表示将主键值赋值给Car对象的id属性。
--><insert id="insertCarUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})</insert></mapper>

Test

    @Testpublic void testInsertCarUseGeneratedKeys(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null,"9991", "凯美瑞", 30.0, "2020-11-11", "燃油车");mapper.insertCarUseGeneratedKeys(car);System.out.println(car);sqlSession.commit();sqlSession.close();}

二、MyBatis参数处理

模块名:mybatis-007-param 表:t_student

1.单个简单类型参数

简单类型包括:

  • byte short int long float double char

  • Byte Short Integer Long Float Double Character

  • String

  • java.util.Date

  • java.sql.Date

需求:根据name查、根据id查、根据birth查、根据sex查

StudentMapper接口

package org.example1.mapper;import org.example1.pojo.Student;import java.util.Date;
import java.util.List;public interface StudentMapper {/*** 当接口中的方法的参数只有一个(单个参数),并且参数的数据类型都是简单类型。* 根据id查询、name查询、birth查询、sex查询*/List<Student> selectById(Long id);List<Student> selectByName(String name);List<Student> selectByBirth(Date birth);List<Student> selectBySex(Character sex);}

<?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="org.example1.mapper.StudentMapper"><!--   parameterType属性的作用:告诉mybatis框架,我这个方法的参数类型是什么类型。mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。SQL语句最终是这样的:select * from t_student where id = ?JDBC代码是一定要给?传值的。怎么传值?ps.setXxx(第几个问号, 传什么值);ps.setLong(1, 1L);ps.setString(1, "zhangsan");ps.setDate(1, new Date());ps.setInt(1, 100);...mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。注意:mybatis框架实际上内置了很多别名。可以参考开发手册。--><select id="selectById" resultType="Student" >select * from t_student where id = #{id}</select><select id="selectByName" resultType="student">select * from t_student where name = #{name}</select><select id="selectByBirth" resultType="student">select * from t_student where birth = #{birth}</select><select id="selectBySex" resultType="student">select * from t_student where sex = #{sex}</select></mapper>

Test

@Testpublic void testSelectBySex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// char --> CharacterCharacter sex = Character.valueOf('男');List<Student> students = mapper.selectBySex(sex);students.forEach(student -> System.out.println(student));sqlSession.close();}// java.util.Date java.sql.Date,他们都是简单类型。@Testpublic void testSelectByBirth() throws Exception{SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date birth = sdf.parse("2017-04-06");List<Student> students = mapper.selectByBirth(birth);students.forEach(student -> System.out.println(student));sqlSession.close();}@Testpublic void testSelectByName(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByName("李四");students.forEach(student -> System.out.println(student));sqlSession.close();}@Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectById(1L);students.forEach(student -> System.out.println(student));sqlSession.close();}

⑴id

parameterType属性的作用:告诉mybatis框架,我这个方法的参数类型是什么类型。mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。SQL语句最终是这样的:select * from t_student where id = ?JDBC代码是一定要给?传值的。怎么传值?ps.setXxx(第几个问号, 传什么值);ps.setLong(1, 1L);ps.setString(1, "zhangsan");ps.setDate(1, new Date());ps.setInt(1, 100);...mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。注意:mybatis框架实际上内置了很多别名。可以参考开发手册。

⑵name

通过测试得知,简单类型对于mybatis来说都是可以自动类型识别的:

  • 也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是ps.setInt()。它可以自动推断。

其实SQL映射文件中的配置比较完整的写法是:

   <select id="selectByName" resultType="student">select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}</select>

其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。

  • javaType:可以省略

  • jdbcType:可以省略

  • parameterType:可以省略

如果参数只有一个的话,#{} 里面的内容就随便写了。对于 ${} 来说,注意加单引号。

⑶date

⑷sex

2.Map参数

需求:根据name和age查询

StudentMapper接口

package org.example1.mapper;import org.example1.pojo.Student;import java.util.Date;
import java.util.List;
import java.util.Map;public interface StudentMapper {/*** 保存学生信息,通过Map参数。以下是单个参数。但是参数的类型不是简单类型。是Map集合。* @param map* @return*/int insertStudentByMap(Map<String, Object> map);}
    <!--<insert id="insertStudentByMap" parameterType="map">--><insert id="insertStudentByMap">insert into t_student(id,name,age,sex,birth,height) values(null,#{姓名},#{年龄},#{性别},#{生日},#{身高})</insert>
    @Testpublic void testInsertStudentByMap(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Map<String,Object> map = new HashMap<>();map.put("姓名", "赵六");map.put("年龄", 20);map.put("身高", 1.81);map.put("性别", '男');map.put("生日", new Date());mapper.insertStudentByMap(map);sqlSession.commit();sqlSession.close();}

测试运行正常。

这种方式是手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。

3.实体类参数

需求:插入一条Student数据

StudentMapper接口

package org.example1.mapper;import org.example1.pojo.Student;import java.util.Date;
import java.util.List;
import java.util.Map;public interface StudentMapper {/*** 保存学生信息,通过POJO参数。Student是单个参数。但是不是简单类型。* @param student* @return*/int insertStudentByPOJO(Student student);}
    <!--<insert id="insertStudentByPOJO" parameterType="student">--><insert id="insertStudentByPOJO">insert into t_student(id,name,age,sex,birth,height) values(null,#{name},#{age},#{sex},#{birth},#{height})</insert>
    @Testpublic void testInsertStudentByPOJO(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// POJO对象Student student = new Student();student.setName("张飞");student.setAge(50);student.setSex('女');student.setBirth(new Date());student.setHeight(10.0);mapper.insertStudentByPOJO(student);sqlSession.commit();sqlSession.close();}

运行正常,数据库中成功添加一条数据。

这里需要注意的是:#{} 里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。

4.多参数

需求:通过name和sex查询

* 这是多参数。
* 根据name和sex查询Student信息。
* 如果是多个参数的话,mybatis框架底层是怎么做的呢?
*      mybatis框架会自动创建一个Map集合。并且Map集合是以这种方式存储参数的:
*          map.put("arg0", name);
*          map.put("arg1", sex);
*          map.put("param1", name);
*          map.put("param2", sex);
package org.example1.mapper;import org.example1.pojo.Student;import java.util.Date;
import java.util.List;
import java.util.Map;public interface StudentMapper {/*** 这是多参数。* 根据name和sex查询Student信息。* 如果是多个参数的话,mybatis框架底层是怎么做的呢?*      mybatis框架会自动创建一个Map集合。并且Map集合是以这种方式存储参数的:*          map.put("arg0", name);*          map.put("arg1", sex);*          map.put("param1", name);*          map.put("param2", sex);** @param name* @param sex* @return*/List<Student> selectByNameAndSex(String name, Character sex);}

执行结果:

异常信息描述了:name参数找不到,可用的参数包括[arg1, arg0, param1, param2] 修改StudentMapper.xml配置文件:尝试使用[arg1, arg0, param1, param2]去参数

    @Testpublic void testSelectByNameAndSex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByNameAndSex("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();}

通过测试可以看到:

  • arg0 是第一个参数

  • param1是第一个参数

  • arg1 是第二个参数

  • param2是第二个参数

实现原理:实际上在mybatis底层会创建一个map集合,以arg0/param1为key,以方法上的参数为value,例如以下代码:

Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}

注意:使用mybatis3.4.2之前的版本时:要用#{0}和#{1}这种形式。

5.@Param注解(命名参数)

可以不用arg0 arg1 param1 param2吗?这个map集合的key我们自定义可以吗?

当然可以。使用@Param注解即可。这样可以增强可读性。 需求:根据name和age查询

package org.example1.mapper;import org.apache.ibatis.annotations.Param;
import org.example1.pojo.Student;import java.util.Date;
import java.util.List;
import java.util.Map;public interface StudentMapper {/*** Param注解。** mybatis框架底层的实现原理:*  map.put("name", name);*  map.put("sex", sex);** @param name* @param sex* @return*/List<Student> selectByNameAndSex2(@Param("name") String name, @Param("sex") Character sex);}
    <select id="selectByNameAndSex2" resultType="Student"><!--使用了@Param注解之后,arg0和arg1失效了--><!--select * from t_student where name = #{arg0} and sex = #{arg1}--><!--使用了@Param注解之后,param1和param2还可以用--><!--select * from t_student where name = #{param1} and sex = #{param2}-->select * from t_student where name = #{name} and sex = #{sex}</select>
    @Testpublic void testSelectByNameAndSex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByNameAndSex("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();}

6.@Param源码分析

MyBatis 的 @Param 注解在源码中主要用于解决 Mapper 接口方法多参数命名问题,其核心逻辑集中在 参数解析 和 参数绑定 过程中。以下是源码分析的关键点:


⑴.@Param 的作用

  • 用途:为方法参数指定名称,使得在 XML 映射文件中可以通过名称引用参数。

  • 场景:当方法参数超过 1 个,或参数需要明确名称时使用。


⑵.源码核心入口:ParamNameResolver

MyBatis 通过 ParamNameResolver 类解析方法参数名称,处理 @Param 注解。以下是关键逻辑:

①构造方法解析参数名
public class ParamNameResolver {// 存储参数索引与名称的映射private final SortedMap<Integer, String> names;public ParamNameResolver(Configuration config, Method method) {final Class<?>[] paramTypes = method.getParameterTypes();final Annotation[][] paramAnnotations = method.getParameterAnnotations();final SortedMap<Integer, String> map = new TreeMap<>();for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {String name = null;// 1. 检查 @Param 注解for (Annotation annotation : paramAnnotations[paramIndex]) {if (annotation instanceof Param) {name = ((Param) annotation).value();break;}}// 2. 无 @Param 时尝试通过反射获取参数名if (name == null) {if (config.isUseActualParamName()) {name = getActualParamName(method, paramIndex);}// 3. 默认回退为 arg0, arg1...if (name == null) {name = String.valueOf(map.size());}}map.put(paramIndex, name);}names = Collections.unmodifiableSortedMap(map);}
}
  • 关键点

    • 优先使用 @Param("name") 定义的名称。

    • 未使用 @Param 时,若配置 useActualParamName=true(默认),尝试通过反射获取参数名(需编译时启用 -parameters)。

    • 最终回退为 arg0arg1, ... 或 param1param2, ...。


②参数包装:getNamedParams

在 SQL 执行时,通过 getNamedParams 方法将参数包装为 Map 或单一对象:

public Object getNamedParams(Object[] args) {final int paramCount = names.size();if (args == null || paramCount == 0) {return null;} else if (!hasParamAnnotation && paramCount == 1) {// 无 @Param 且仅一个参数:直接返回该参数对象return args[names.firstKey()];} else {final Map<String, Object> param = new ParamMap<>();for (Map.Entry<Integer, String> entry : names.entrySet()) {// 组合名称:@Param 值 + "param" + 索引param.put(entry.getValue(), args[entry.getKey()]);param.put("param" + (entry.getKey() + 1), args[entry.getKey()]);}return param;}
}
  • 逻辑说明

    • 无 @Param 且单参数:直接返回参数对象(如 User),XML 中可直接引用其属性。

    • 有 @Param 或多参数:包装为 ParamMap,包含两种键:

      • 自定义名称:通过 @Param("name") 定义的键。

      • 通用名称:如 param1param2(兼容旧版本)。


⑶.SQL 参数绑定

在 DefaultParameterHandler 中,通过 ParameterHandler 处理参数映射:

public class DefaultParameterHandler implements ParameterHandler {public void setParameters(PreparedStatement ps) {// 从 ParamNameResolver 获取参数 MapObject parameterObject = boundSql.getParameterObject();// 遍历参数映射,替换 SQL 中的 #{name}for (ParameterMapping paramMapping : parameterMappings) {String property = paramMapping.getProperty();Object value;if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 从 ParamMap 中按名称获取值MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(property);}// 设置 PreparedStatement 参数typeHandler.setParameter(ps, i + 1, value, paramMapping.getJdbcType());}}
}

⑷.示例场景

①. 使用 @Param 的 Mapper 方法
User selectUser(@Param("name") String name, @Param("age") int age);
  • 生成的 ParamMap

    {"name": "Alice","param1": "Alice","age": 25,"param2": 25
    }
② XML 中的引用
<select id="selectUser">SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>

⑸.关键设计思想

  1. 兼容性:支持 param1 等传统占位符。

  2. 灵活性:允许通过 @Param 自定义名称,提升可读性。

  3. 性能优化:单参数直接传递,避免不必要的 Map 包装。


⑹.总结

@Param 的源码实现通过 ParamNameResolver 解析参数名称,并在执行时通过 ParamMap 统一处理多参数场景。这一机制使得 MyBatis 能够灵活适配不同参数命名需求,同时保持与旧版本的兼容性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词