从浏览器中发送来的数据,要通过service传输到xxxMapper.xml映射文件中,那么Mapper映射文件要如何来获取传输来的参数,来实现SQL语句的拼接呢。
MyBatis获取参数的两种方式
${}:本质是字符串拼接
#{}:本质是填充占位符
可以通过KaTeX parse error: Expected 'EOF', got '#' at position 5: { }和#̲{ }**,在**大括号内写入…{ }的单引号问题
public interface ParamMapper {
// 根据用户名查询人员信息
User getUserByName(String username);
}
// 注意:真实代码中,两个方式只能写一种
<mapper namespace="com.atguigu.mybatis.mapper.ParamMapper">
<select id="getUserByName" resultType="User">
// 方式一
select * from t_user where username = #{username}
// 方式二
select * from t_user where username = '${username}'
</select>
</mapper>
此时在框架底层,MyBatis会将这些参数放在一个map集合中,以下述两种方式进行存储
方式1:以arg0,arg1,…为键,以参数为值
方式2:以param1,param2,…为键,以参数为值
故,在#{ }和${ }内放入的是上述两种键名称,不可以是其它的
<mapper namespace="com.atguigu.mybatis.mapper.ParamMapper">
<select id="checkLogin" resultType="user">
select * from t_user where username = #{arg0} and pwd = #{arg1}
</select>
</mapper>
可以手动将这些参数放在一个map中存储,然后将map中的键放在#{ }内
public interface ParamMapper {
User checkLoginByMap(Map<String,Object> map);
}
<mapper namespace="com.atguigu.mybatis.mapper.ParamMapper">
<select id="checkLoginByMap" resultType="user">
select * from t_user where username = #{username} and pwd = #{password}
select>
mapper>
public class ParamTest {
@Test
public void test4() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParamMapper mapper = sqlSession.getMapper(ParamMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("username","李四");
map.put("password","123456");
User userByName = mapper.checkLoginByMap(map);
System.out.println("userByName = " + userByName);
}
}
只需要通过#{ } 和 ${ },以属性发的方式访问属性值即可。
public interface ParamMapper {
int insertUser(User user);
}
<insert id="insertUser">
insert into t_user value (null,#{username},#{pwd},#{age},#{sex},#{email})
insert>
public class ParamTest {
@Test
public void test4() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParamMapper mapper = sqlSession.getMapper(ParamMapper.class);
int i = mapper.insertUser(new User(null, "张三", "123456", 23, '女', "[email protected]"));
System.out.println("i = " + i);
}
}
使用@Param注解来命名各参数,MyBatis会自动将@Param注解中的值存入到参数map中,即我们可以通过以下两种方式获取传来的参数值
public interface ParamMapper {
User checkLoginByParam(@Param("username") String username, @Param("password") String pwd);
}
<insert id="insertUser">
select * from t_user where username = #{username} and pwd = #{password}
insert>
public class ParamTest {
@Test
public void test6() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParamMapper mapper = sqlSession.getMapper(ParamMapper.class);
User userByName = mapper.checkLoginByParam("李四","123456");
System.out.println("userByName = " + userByName);
}
}
在测试类中,getMapper方法的底层使用的是代理模式
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
// 利用反射,调用invoke方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) {
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 1.command:命令,包含mapper接口的指定方法全类名和sql类型
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 2.判断select类型语句的返回值类型
case SELECT:
// 2.1 如果返回值为空
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 2.2 如果返回多条值
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 2.3 如果返回map集合
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 2.4 如果返回cursor游标
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 2.5 其它返回值
} else {
// 将方法的参数转换为sqlCommand参数
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// 2.5
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
#####解析 mapper接口中被调用方法的参数
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
// 1.利用反射,获取方法的参数类型数组
final Class<?>[] paramTypes = method.getParameterTypes();
/*
2.获取参数的注解,参数有多个,每个参数的注解也有多个,故返回的是一个二维数组
第一维:参数
第二维:参数的注解
*/
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
// 3.参数注解二维数组的第一维的长度
int paramCount = paramAnnotations.length;
// 4.循环每一个参数,并添加到map中
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// 4.1 获取每一个参数的名字
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 4.2 如果该参数的注解类型是Param,
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 4.3 则获取该参数的注解属性值,赋值给name
name = ((Param) annotation).value();
// 4.4 只要有一个参数的注解类型是Param,就跳出for循环
break;
}
}
if (name == null) {
// @Param was not specified.
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
// 4.5 将参数索引为键(从0开始),该参数注解为Param的参数名为值,添加到map集合中
map.put(paramIndex, name);
}
// 5.将map转型后赋值给names
names = Collections.unmodifiableSortedMap(map);
}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
// 1.如果有Param注解的参数
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 2.将names集合中的每一对键值对处理后放入param集合
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 2.1 param集合中:键为names集合的值(即参数名),值为args[](即参数中的值)
param.put(entry.getValue(), args[entry.getKey()]);
// 2.2 设置参数名:param+索引
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
/*
2.3 如果names中不包含上一条设置的参数名,则将该值添加到param集合中
这就是为什么mybatis在获取有@Param注解的参数时,传入#{}中的可以是@Param中的参数
名,也可以是param1,param2...
*/
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
上述五种传参的情况,可以归结为两种