MyBatis其他知识点

  1. 使用MyBatis实现查询功能
    只要是使用MyBatis框架开发数据操作功能,都是编写抽象方法及对应的SQL语句!

【更新】关于抽象方法的声明原则

如果即将执行的是INSERT、UPDATE、DELETE类型的操作,则使用Integer/int作为返回值类型,表示受影响的行数,当然,如果不关心这个返回值,也可以将返回值类型声明为void,但是,并不推荐;如果即将执行的是SELECT类型的操作,返回值类型使用期望的数据类型,同时,需要保证该类型可以封装所需要的查询结果,如果查询结果有多条记录,则应该使用List集合作为抽象方法的返回值类型;
方法名称可以自定义,不允许重载;
参数列表可按需设计。
假设当前需要实现“统计当前数据表中有多少条用户数据”,则可以声明抽象方法为:

Integer count();
接下来,应该在XML文件中配置以上抽象方法对应的SQL语句。

关于配置SQL时需要注意的

应该按需选择所配置的节点,例如执行INSERT操作时就应该使用节点,尽管执行INSERT操作时也可以使用或,但是,绝对不要这样做,如果要执行的是SELECT操作,必须使用节点;
无论是、、、中的任何一个,必须配置id属性,取值就是对应的抽象方法的名称;
如果配置的是节点,必须配置resultType或resultMap属性中的某1个(必须二选一),使用resultType时,表示指定返回值类型,取值为类型的全名,如果抽象方法的返回值类型是List集合类型的,则resultType的值应该是List集合中的元素的类型,也就是说,如果返回值类型是List类型的,则resultType的值应该是User的类型;
所以,关于“统计当前数据表中有多少条用户数据”配置SQL语句的代码为:

SELECT COUNT(*) FROM t_user 最后,编写并执行单元测试:

@Test
public void count() {
Integer count = userMapper.count();
System.out.println(“count=” + count);
}
另外,例如需要实现“查询id=?的用户的数据”,可以设计抽象方法为:

User findById(Integer id);
然后,配置的映射(与抽象方法对应的在XML中配置的节点)为:

SELECT * FROM t_user WHERE id=#{id} 最后,测试:

@Test
public void findById() {
Integer id = 10;
User user = userMapper.findById(id);
System.out.println(user);
}
2. 关于抽象方法中使用多个参数的问题
假设需要实现“将id=?的用户的邮箱改为?”,则需要执行的SQL语句大致是:

UPDATE t_user SET email=? WHERE id=?
由于以上SQL语句中有2个参数,所以,在设计抽象方法时,抽象方法的参数列表中应该也有2个参数,例如:

Integer updateEmailById(Integer id, String email);
在配置映射时,可以是:

UPDATE t_user SET email=#{email} WHERE id=#{id} 然后,编写并执行单元测试:

@Test
public void updateEmailById() {
Integer id = 10;
String email = “user10@qq.com”;
Integer rows = userMapper.updateEmailById(id, email);
System.out.println(“rows=” + rows);
}
以上代码执行时会出现错误:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘email’ not found. Available parameters are [arg1, arg0, param1, param2]

Caused by: org.apache.ibatis.binding.BindingException: Parameter ‘email’ not found. Available parameters are [arg1, arg0, param1, param2]
也就是说,在调用抽象方法时,给出了2个参数的值,分别是id的值和email的值,这2个值在MyBatis处理时,参数名称其实分别是arg0和arg1,或者,使用param1和param2也可以访问到这2个参数!

关于以上问题,其根本原因在于Java语言源代码.java文件在执行之前,需要被编译为.class字节码文件,在这个编译过程中,如果没有特殊的干预的情况下,所有局部变量的变量名都是不会被保存的,所以,即使在设计抽象方法时,使用了id、email这样的参数名,其实,在最终运行的.class文件中,根本就没有这样的名称!

当抽象方法的参数只有1个时,MyBatis会自动的使用唯一的那一个参数,所以,在配置SQL映射时,使用的#{}占位符中的名称根本就不重要!在开发时,推荐使用规范的名称,但是,从代码是否可以运行的角度来说,这个占位符中写什么都不重要!

在MyBatis的处理过程中,可以根据arg或param作为前缀,按照顺序来确定参数的值,所以,使用第1个参数时,可以使用arg0或param1作为名称,第2个可以使用arg1或param2 作为名称,如果有更多参数,顺序编号即可,序号可参考抽象方法的声明!

当然,在配置SQL映射时,使用arg0、arg1或param1、param2这样的名称,并不利于代码的阅读和理解,Mybatis框架提供了@Param注解,可以在抽象方法的参数列表中,在每个参数之前添加该注解,并在注解中配置参数的名称:

Integer updateEmailById(
@Param(“id”) Integer id,
@Param(“email”) String email
);
后续,在配置SQL映射时,使用的#{}格式的占位符中使用的名字就必须是以上@Param注解中配置的名称!

MyBatis实现以上做法的本质其实就是基于注解中配置的参数和调用方法时给出的参数值封装了1个Map!

当然,以上实现原理可以不必过于关心,只需要记住:在使用MyBatis框架,设计抽象方法时,如果参数的数量超过1个(有2个或更多个),就为每一个参数都添加@Param注解,并且,在后续配置SQL映射时,使用的#{}中就使用@Param注解中配置的名称!

  1. 动态SQL–foreach
    动态SQL:根据调用方法时给出的参数不同,最终生成的SQL语句会不相同!

假设需要实现“删除id=?, id=?, id=?的数据”,即“一次性删除若干条数据”,具体需要删除几条、哪几条,对于开发人员来说,都是不确定的。

实现这样的操作,需要执行的SQL语句大致是:

DELETE FROM t_user WHERE id IN (?,?,?)
以上SQL语句中,问号的数量与值都是开发人员无法确定的,但是,可以肯定的是“这里的问号表示的都是id值”。

实现这个功能,在设计抽象方法时,可以将若干个问号的值,也就是若干个id的值,使用集合或数组来表示,例如:

Integer deleteByIds(List ids);
或:

Integer deleteByIds(Integer[] ids);
甚至,还可以将参数声明为可变参数,例如:

Integer deleteByIds(Integer… ids);
完成了抽象方法的声明之后,就需要配置以上抽象方法的映射!配置映射时,需要使用节点实现动态SQL中的遍历,即遍历以上方法中的参数值,以生成?,?,?这样的SQL语句片段!

例如,可以配置为:

DELETE FROM t_user WHERE id IN ( #{id} ) 在以上配置中,各属性的作用:

collection:表示被遍历的对象,当抽象方法的参数只有1个且没有添加@Param注解时,如果参数的类型是List集合类型的,则取值为list,如果参数的类型是数组类型的,则取值为array;否则,取值为@Param注解中配置的名称;
item:在遍历过程中,被遍历到的数据的名称,将根据这个名称来使用被遍历到的数据,所以,在的子级,将根据这个属性配置的值来使用数据;
separator:分隔符,用于分隔遍历时生成的多个占位符(也可以理解为遍历产生的多个值);
open与close:遍历生成的SQL语句片段的起始字符串和结束字符串。
4. 动态SQL–if
关于的配置语法格式是:

SQL语句片段 使用可以根据判断条件不同而得到不同的SQL语句。

在使用时,如果有多个条件存在逻辑关系,可以使用and表示“与”,使用or表示“或”。

在MyBatis中的并没有对应的else标签,所以,只能判断“符合条件时应该怎么执行”,并没有不符合条件时的做法!

如果一定需要实现if…else的效果,需要使用系列的标签,基本格式是:

满足条件时的SQL语句片段 不满足条件时的SQL语句片段 5. 关于#{}和${}格式的占位符 在MyBatis中,配置SQL语句时,可以使用#{}或${}格式的占位符,用于表示参数的值。

使用#{}格式的占位符时,只能表示某个值,不可以表示SQL语句中的某个片段!使用这种占位符时,MyBatis在处理过程中,是使用预编译的做法,即:使用占位符来填充整个SQL语句,此时,并不关心占位符对应的值是多少,就直接将这样的SQL语句交给数据库进行词法分析、语义分析、编译,编译之后,再将占位符对应的值代进编译好的SQL语句中并执行!

使用${}格式的占位符时,可以表示SQL语句中的任何部分,可以是某个值,也可以是SQL语句中的某个片段!使用这种占位符时,MyBatis在处理过程中,是先将占位符对应的值拼接到SQL语句中,然后,将整个SQL语句交给数据进行词法分析、语义分析、编译,如果无误,就可以直接执行SQL语句,如果出现语法错误,就会导致程序运行过程中出现异常!

使用#{}格式的占位符时,由于使用了预编译的做法,所以,这种处理方式是安全的,而${}占位符是先拼接SQL语句再执行的过程,并没有预编译的处理,所以,存在SQL注入的风险的!

所以,虽然使用${}的占位符可以实现的效果看似更多,但是,需要考虑数据类型的问题(例如字符串类型的值需要自行添加一对单引号),同时还存在SQL注入的风险,一般不推荐使用,应该优先使用#{}格式的占位符!

  1. 解决名称不匹配导致查询不到数据的问题
    首先,对原有的数据表做调整,增加一个新的字段,表示该用户的部门信息:

ALTER TABLE t_user ADD COLUMN department_id INT;
然后,为现有的用户数据分配部门:

UPDATE t_user SET department_id=1 WHERE id IN (12,17);
UPDATE t_user SET department_id=2 WHERE id IN (3,9);
UPDATE t_user SET department_id=3 WHERE id IN (10,16);
由于数据表中添加了新的字段,在对应的User类中也应该添加新的属性:

private Integer departmentId; // 另补充SET/GET方法,重新生成toString()
接下来,原有的查询功能中,目前并不可以查询到各用户的部门ID值!主要原因是:MyBatis框架在执行查询操作时,会根据查询结果中的列名,将数据封装到结果对象的同名属性中(当然,这个过程其实依然是通过SET/GET方法来实现的)!例如在查询结果中存在username的列,MyBatis就会把这一列的数据封装到User类的username属性中!所以,可以简单的认为:MyBatis要求查询结果的列名与封装结果的类中的属性名保持一致,才可以正确的自动封装查询结果!

当前,在执行查询时,使用的SQL语句是:

SELECT * FROM t_user; // 暂不考虑后面的WHERE子句等部分
由于使用的是星号表示要查询的字段列表,则查询结果的列名与字段的名称是完全一致的,所以,部门ID对应的列名是department_id,而在User类中并没有这个名字对应的属性,在User类存在的是departmentId,这2个名字并不相同,所以,MyBatis无法实现自动封装!

为了使得MyBatis能够自动封装部门ID的数据,就必须保持名字的统一,但是,肯定不推荐修改User中的属性名或SET方法的名称,那么,就可以调查询结果中的列名,可以通过在SQL语句中定义别名来实现:

SELECT id,username,password,age,phone,email,department_id AS departmentId FROM t_user;
附:关于可变参数
// 关于可变参数:
// 1. 每个方法中最多只能有1个可变参数,且必须是方法的最后一个参数;
// 2. 调用这种方法时,可变参数的值的数量可以是0个或若干个;
// 3. 在方法内部处理参数值时,可变参数的值就是一个数组。
public static void sum(int… numbers) {
int result = 0;
for (int i = 0; i < numbers.length; i++) {
result += numbers[i];
}
System.out.println(result);
}

  1. 使用解决名称不匹配导致无法查询到某些字段的数据的问题
    MyBatis框架在处理查询结果时,要求查询结果中的列名(Column)与封装结果的类的属性名(Property)保持一致!如果在查询的SQL语句中,没有为查询的字段列表取别名,则查询结果中的列名就是字段名(Field)。

字段名(Field):创建数据表时指定的名称,通常,在讨论某个数据表的结构时,就会使用该名称进行描述;

列名(Column):查询结果中,在控制台显示的表格中,第一横排显示的名称;

属性名(Property):Java类中的属性的名称。

当没有自定义别名时,如果名称不匹配,就会导致对应的列的数据无法被封装到查询结果中,此时,可以配置节点,以指导MyBatis框架完成封装。

关于 的配置如下:

​ SELECT * FROM t_user WHERE id=#{id} 注意:如果要使用来决定如何封装查询结果,相关的必须配置resultMap属性,而不能使用resultType属性,并且,在节点中,resultMap和resultType属性是二选一的,必须也只能配置其中的1个!

在以上配置中,如果查询结果的列名与封装结果的类的属性名本来就是相同,则可以不必配置对应的节点!即,以上配置可以简化为:

另外,关于主键的配置,推荐使用节点,而不是节点,例如: 关于主键,无论使用节点,还是使用节点来配置,运行效果是没有区别的,但是,有利MyBatis框架实现缓存机制!

小结:如果出现查询结果的列名与封装结果的类的属性名不一致的问题,可以使用resultType指定查询结果的类的全名,并在查询的SQL语句中,使用自定义别名,保证名称一致,或者,也可以使用resultMap进行配置,在查询的SQL语句中,使用星号(*)表示查询的字段列表即可,这2种解决方案二选一即可!

  1. 关联表查询-1
    准备工作:先保证当前项目中使用的数据库中有t_department数据表,数据表中有id和name字段,并且,有一定的测试数据。

假设需要实现:查询某个用户的信息,并且,要求显示该用户所归属的部门的名称!

实现以上功能,需要执行的SQL语句大致是:

SELECT
t_user.*, name
FROM
t_user
LEFT JOIN
t_department
ON
t_user.department_id=t_department.id
WHERE
t_user.id=3;
在实际代码时,还是应该先设计抽象方法,但是,目前没有哪种数据类型能够封装此次的查询结果!所以,需要自行创建新的VO类,用于封装查询结果!

所以,创建名为UserVO的类:

public class UserVO {

private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
private Integer departmentId;
private String departmentName;

// 补充SET/GET方法和toString()方法

}
与数据表结构保持对应的类,称之为实体类(Entity),例如案例中使用的User类就是实体类;

与查询结果相对应的类,称之为VO类(Value Object),例如以上创建的UserVO类,这种类的设计原则就是:查询结果中有哪些数据,在VO类中就设计哪些属性。

其实,实体类与VO的编码方式、代码结构几乎是一样的,只是定位不同。

然后,就可以设计抽象方法为:

UserVO findVOById(Integer id);
接下来,需要配置以上抽象方法映射的SQL语句,也就是此前分析的SQL语句!

一般,在使用关联表查询时,并不推荐使用星号()表示要查询的字段列表,同时,有很多数据表中的字段与类中的属性名并不是完全一致的(就好比department_id的问题),所以,一般在编写关联表查询的SQL语句时,就是不使用星号()并且将名称不一致的字段取别名!

所以,要配置的SQL映射是:

SELECT t_user.id, username, password, age, phone, email, department_id AS departmentId, name AS departmentName FROM t_user LEFT JOIN t_department ON t_user.department_id=t_department.id WHERE t_user.id=#{id} 3.关联表查询-2 假设需要实现:查询某个部门的信息,并且,需要显示该部门有哪些用户。

需要执行的SQL语句大致是:

SELECT
*
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=1;
在编写代码时,依然需要先设计抽象方法,而设计抽象方法之前,就需要先创建新的VO类,用于封装此次的查询结果!所以,先创建DepartmentVO类:

public class DepartmentVO {
private Integer id;
private String name;
private List users; // 每个部门可以有若干个用户

// 补充SET/GET方法和toString()方法

}
此前使用的UserMapper.java都是配置处理用户数据的相关功能,此次,查询的主体是部门**的数据,抽象方法应该另外创建接口来存放,以便于管理数据!(可以放在同一个接口中,但是,不推荐这样做)

所以,应该先创建DepartmentMapper.java接口文件,然后,这个接口中添加抽象方法:

public interface DepartmentMapper {

DepartmentVO findVOById(Integer id);

}
完成接口中的抽象方法后,就应该开始配置SQL映射,此前的SomeMapper.xml的根节点对应的是UserMapper接口,所以,将无法对应新创建的DepartmentMapper接口中的抽象方法,并且,出于规范管理代码的要求,也应该将管理部门数据的SQL语句写在新的XML文件中!

所以,先将SomeMapper.xml重命名为UserMapper.xml,再将UserMapper.xml复制得到DepartmentMapper.xml并删除其中的配置!

接下来的任务就是需要配置SQL映射,而此次的查询时,某个部门可能有多个用户,所以结果可能有多条,但是,抽象方法的返回值是1个对象,那么,就存在MyBatis不知道如何将若干个查询结果封装到1个对象中去!所以,在配置时,就需要结合需要执行的SQL语句并使用来完成配置:

<?xml version="1.0" encoding="UTF-8" ?>





















SELECT
t_user.*,
t_department.id AS did, name
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=#{id}



最后,编写并执行单元测试:

public class DepartmentTests {

private ClassPathXmlApplicationContext ac;
private DepartmentMapper mapper;

@Test
public void findVOById() {
Integer id = 1;
DepartmentVO department = mapper.findVOById(id);
System.out.println(department);
}

@Before
public void doBefore() {
ac = new ClassPathXmlApplicationContext(“spring-dao.xml”);
mapper = ac.getBean(“departmentMapper”, DepartmentMapper.class);
}

@After
public void doAfter() {
ac.close();
}

}
4. 关于MyBatis框架的小结
MyBatis框架主要的作用:可以简化数据库编程;

了解使用MyBatis框架时需要添加的依赖;

认识关于MyBatis框架的配置,包括db.properties和spring-dao.xml中的配置;

掌握抽象方法的声明原则:

【返回值】如果需要执行的是INSERT / DELETE / UPDATE类型的操作,使用Integer作为返回值类型;如果需要执行的是SELECT类型的操作,使用期望的数据类型作为返回值类型,在查询时,如果是单表查询某数据,可以使用实体类作为返回值类型,如果是多表查询,应该先创建VO类,然后使用VO类作为返回值类型,如果查询多条数据,使用List<?>作为返回值类型;
【方法名称】自定义,但是,不允许重载;
【参数列表】按需设计,可以理解为SQL语句中哪些不确定的值,就可以设计出哪些参数,如果参数数据较多,可以将这些参数封装到1个对象中,特别是插入数据操作,必须使用封装的数据类型;最终,如果参数的数量超过1个,必须使用@Param注解配置每一个参数的名称,该名称将用于配置SQL语句时的#{}的占位符中;
在配置XML映射文件时,必须保留顶部的XML声明与文档类型声明语句,并且,声明语句之前不可以有任何符号,包括空格、空白行;

在配置XML映射文件时,根节点必须配置namespace属性,取值是对应的接口的全名;

在配置XML映射文件中的SQL语句之前,必须添加匹配的、、、节点,这些节点都必须指定id属性,取值是抽象方法的名称;如果使用的是节点,必须且只能配置resultType或resultMap中的某1个属性,如果查询多条数据,也不必将类型声明为List集合类型的,只需要声明为List集合中的元素的类型即可;

在配置XML映射文件中的SQL语句时,参数使用#{}格式的占位符来表示,当抽象方法的参数只有1个时,占位符中的名称可以是任意名称;当抽象方法的参数有多个时,占位符中的名称必须是抽象方法的参数之前@Param注解配置的名称;

在使用占位符时,应该优先选取#{}格式的占位符,这种占位符在最终运行时,是预编译的,所以没有SQL注入的风险,且不需要关注参数的数据类型的问题;如果占位符表示的不是某个值,而是SQL语句的某个片段,可以使用${}格式的占位符,这种占位符在处理时是拼接到SQL语句中的,不是预编译的,所以,存在SQL注入的风险,并且需要自行考虑值的数据类型的问题,一般不推荐使用;

在处理查询时,MyBatis框架要求列名与属性名一致,可使用的解决方案有2种:在SQL语句中自定义别名,并在节点配置resultType为对应的封装结果的类型即可;在SQL语句中不需要自定义别名,另使用配置列名与属性名的对应,并在节点配置resultMap属性,取值为的id的值;

理解实体类与VO类的定位,这2种类型的区别;

如果查询结果中存在1对多的对应关系,查询到的多条结果需要封装1个非List的对象中,在配置时就需要使用节点来配置“多”的数据关系;

什么时候需要自定义别名:

【列名与属性名不同】在查询时,存在列名与属性名不一致的情况,却没有使用进行配置,则可以使用自定义别名使得列名与属性名保持一致;
【多个列的名称相同】在关联表查询时,多张表中都有相同的字段名,会导致查询结果中出现相同的列名,可能进而导致MyBatis不知道如何封装(有多列的名称相同时,MyBatis默认取最左侧的那列的名称),就需要使用别名使得查询结果的列名都互不相同;
什么时候需要使用:

【列名与属性名不同】在查询时,存在列名与属性名不一致的情况,却没有自定义别名(例如直接使用星号(*)表示查询的字段列表),则可以配置;
【存在一对多的关系】在关联表查询中,如果需要将查询到的多条结果封装在1个非List的对象中,必须配置;
在配置时,如果只是单表数据查询,如果列名与属性名相同,不需要通过节点进行配置的;如果是关联表查询,无论列名与属性名是否相同,都必须通过节点进行配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值