目录
resultMap-关联查询-assoiation定义关联对象封装规则
源码分析,mybaits如何处理参数
========================结合源码,mybatis怎么处理参数==========================
Debug : Employee employee = mapper.getEmpByIdAndLastName(7, "wangzhaojun"); 进入方法:进入代理类MapperProxy->MapperMethod->ParamNameResolver
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
#{key}就可以取出map中的值;
(@Param("id")Integer id,@Param("lastName")String lastName);
参数args:Object[] args = [7,"wangzhaojun"]
ParamNameResolver解析参数封装map的;
//1、names:{0=id, 1=lastName};构造器的时候就确定好了
确定流程:
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值:
标注了param注解:注解的值
没有标注:
1.全局配置:useActualParamName(jdk1.8):name=参数名
2.name=map.size();相当于当前元素的索引
{0=id, 1=lastName,2=2}
args【1,"Tom",'hello'】:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、参数为null直接返回
if (args == null || paramCount == 0) {
return null;
//2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
//3、多个元素或者有Param标注
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//4、遍历names集合;{0=id, 1=lastName,2=2} args:Object[] args = [7,"wangzhaojun"]
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names集合的value作为key; names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)param
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
返回的param
0 = {HashMap$Node@1722} "lastName" -> "wangzhaojun"
1 = {HashMap$Node@1723} "id" -> "7"
2 = {HashMap$Node@1724} "param1" -> "7"
3 = {HashMap$Node@1725} "param2" -> "wangzhaojun"
总流程:
1.先判断是什么方法 select update等 其他update 直接
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
2.如果是select 有多项判断有无返回值,是多个还是单个返回值 最后再调用
然后拼接参数 param 供给下面方法使用。
result = sqlSession.selectOne(this.command.getName(), param);
参数处理-$和#取值的区别
#{}:可以获取map中的值或者pojo对象属性的值;
${}:可以获取map中的值或者pojo对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=2 and last_name=?
区别:
#{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中;会有安全问题;
大多情况下,我们去参数的值都应该去使用#{};
原生jdbc不支持占位符的地方我们就可以使用${}进行取值 , 不支持预编译的。
比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更丰富的用法:
规定参数的一些规则:
javaType、 jdbcType、 mode(存储过程)、 numericScale、
resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);
jdbcType通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);
比如在插入一条数据,其中一个字段的值为null。就会报错。
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;两种办法
1、#{email,jdbcType=OTHER};
2、jdbcTypeForNull=NULL
<setting name="jdbcTypeForNull" value="NULL"/>
select_返回List
•
Select元素来定义查询操作。
• Id:唯一标识符。 – 用来引用这条语句,需要和接口的方法名一致
•
parameterType:参数类型。 – 可以不传,MyBatis会根据TypeHandler自动推断
•
resultType:返回值类型。 – 别名或者全类名,如果返回的是集合,定义集合中元
素的类型。不能和resultMap同时使用。
第一种情况:
<!-- public List<Employee> getEmpsByLastNameLike(String lastName); -->
<!--resultType:如果返回的是一个集合,要写集合中元素的类型 -->
<select id="getEmpsByLastNameLike" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
第二种情况:
//多条记录封装一个map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
第二种情况中的resultType指定的是每个记录的封装的返回类型。
自定义结果映射规则-resultMap
1、全局setting设置
– autoMappingBehavior默认是PARTIAL
,开启自动映射
的功能。唯一的要求是列名和javaBean属性名一致。
– 如果autoMappingBehavior设置为null则会取消自动映射
– 数据库字段命名规范,POJO属性符合驼峰命名法,如
A_COLUMN->
aColumn,我们可以
开启自动驼峰命名规
则映射功能
,
mapUnderscoreToCamelCase=true。
– 也可以通过起别名的方式。
<!--自定义某个javaBean的封装规则
type:自定义规则的Java类型
id:唯一id方便引用
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MySimpleEmp">
<!--指定主键列的封装规则
id定义主键会底层有优化;
column:指定哪一列
property:指定对应的javaBean属性
-->
<id column="id" property="id"/>
<!-- 定义普通列封装规则 -->
<result column="last_name" property="lastName"/>
<!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
<!-- resultMap:自定义结果集映射规则; -->
<!-- public Employee getEmpById(Integer id); -->
<select id="getEmpById" resultMap="MySimpleEmp">
select * from tbl_employee where id=#{id}
</select>
resultMap-层联查询
场景一:
查询Employee的同时查询员工对应的部门
创建dept表,添加外键
create table tbl_dept (
id int(11) PRIMARY key AUTO_INCREMENT,
dept_name varchar(255)
)
alter table tbl_employee add column d_id int(11)
alter table tbl_employee add constraint fk_emp_dept foreign key(d_id) references tbl_dept(id)
修改Emp类,新建dept
@Alias("emp")
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
private Department dept;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, String gender) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email="
+ email + ", gender=" + gender + "]";
}
}
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
public List<Employee> getEmps() {
return emps;
}
public void setEmps(List<Employee> emps) {
this.emps = emps;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName
+ "]";
}
}
接口类和映射文件修改
层联属性封装查询:
接口类 : public Employee getEmpAndDept(Integer id);
映射文件 :
<resultMap type="emp" id="empDiff">
<id column="id" property="id" />
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="did" property="dept.id" />
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<select id="getEmpAndDept" resultMap="empDiff">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>
测试结果
测试报错,如下:没有找到构造方法。
SQL: SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id, d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d WHERE e.d_id=d.id AND e.id=?
### Cause: org.apache.ibatis.executor.ExecutorException: No constructor found in com.atguigu.mybatis.bean.Employee matching [java.lang.Integer, java.lang.String, java.lang.String, java.lang.Integer, java.lang.Integer, java.lang.String]
在Employee类加上一下代码即可。
public Employee() {
super();
}
注:
注:Mybatis查询结果映射实体类必须有空构造函数!!!
提问:Mybatis查询结果映射到实体类的时候,实体类为什么必须有一个空的构造函数?
答:Mybatis框架会调用这个默认构造方法来构造实例对象,即实体类需要通过Mybatis进行动态反射生成。
反射的Class.forName("className").newInstance();需要对应的类提供一个无参构造函数。
备注:
如果在类中没有提供任何构造方法,虚拟机会自动提供默认构造方法(无参构造器),但是如果提供了其他有参数的构造方法的话,虚拟机就不再为提供默认构造方法,所以默认的构造方法不是必须的,只在有多个构造方法时才是必须的显式声明的。
resultMap-关联查询-assoiation定义关联对象封装规则
<!--
使用association定义关联的单个对象的封装规则;
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp2">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<!-- association可以指定联合的javaBean对象
property="dept":指定哪个属性是联合的对象
javaType:指定这个属性对象的类型[不能省略]
-->
<association property="dept" javaType="com.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>