Mybatis增删查改Mapper参数及部分源码分析
代码样例
<select id="getUserByIdToMap" resultType="map" parameterType="int">
select * from user where id = #{id}
</select>
<insert id="addUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into user (name,password)values(#{name},#{password})
</insert>
<update id="updateUser" parameterType="String">
update user set password =#{password} where name =#{name}
</update>
<delete id="deleteUserById">
delete from user where id = #{id}
</delete>
curd的返回值
增删改
三种方式返回值可是设置为:Long Integer Boolean
insert
获取自增主键的方法(Mysql数据库)
在insert标签中配置这两个属性
```keyProperty="id"``` id代表返回给类的属性名称(数据库中被映射到id的列 必须支持自增 auto_incement)
通过user.getId();便可以得到 mybatis为你设置的自增主键名称
注意:oracle数据库不支持自增
##### Oracle序列
oracle数据库的主键是从序列中拿到的
可以通过select 语句拿到下一个主键值 如```select USER_SEQ.nextval from user ```
然后再插入即可
##### 批量插入数据的方法
1.可以通过动态sql的方式 拼接sql语句 实现插入批量数据(不推荐,因为sql语句的长度是有限的)
2.通过修改mybatis的全局配置文件中**defaultExecutorType**来实现批量查询(但是这会修改所有crud操作,使之成为批量操作,不推荐)
| defaultExecutorType | Configures the default executor. SIMPLE executor does nothing special. REUSE executor reuses prepared statements. BATCH executor reuses statements and batches updates. | SIMPLE REUSE BATCH | SIMPLE |
| ------------------- | ------------------------------------------------------------ | ------------------ | ------ |
| | | | |
3.使用支持批量查询的sqlSession(**推荐**)
sqlSessionFactory.openSession(ExecutorType.BATCH);
用该SqlSession进行操作 便是批量操作!
对比方式1可以**节省60%的时间**
#### 查询
select 的返回值是列表。resultType="user" resultType对象仍然要写pojo mybatis会自动封装成列表并返回
返回map类型(map中的每个元素代表数据库中的一个行记录)
//通过注解指定 key的名称
@MapKey("name")
Map
##### 返回map类型 每个map封装好一条行记录 key是列名 value是记录值
Map
可以通过设置resultType 指定返回值类型
亦可以通过resultMap指定返回值类型
以上两种方式不共存
##### resultMap代码示范:
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Dao.UserMapperPlus">
<!--<select id="getUserById" resultType="user">-->
<!--select * from user where id = #{id}-->
<!--</select>-->
<!--当数据库当列名column Name 和java bean中的property
不一致的时候 比如数据库的列名叫做 e_mail 而我们的java bean属性叫做 eMail Mybatis就不会映射
我们可以
1。 mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,
即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
默认是false 我们需要在mybatis全局的setting中设置 为true
2。手动定制 映射规则
代码如下 只为演示 数据库中的表并有没上述问题
-->
<resultMap id="userMap" type="model.User">
<!--id标签 定义为主键底层会有优化-->
<id column="id" property="id"/>
<!--result标签 定义普通列封装规则-->
<result column="name" property="name"/>
<!--其他不指定的列会被自动封装-->
</resultMap>
<select id="getUserById" resultMap="userMap">
select * from user where id = #{id}
</select>
</mapper>
带有级连属性的resultMap使用方法:
方法1: 在resultMap中显式指明级连属性的property
方法2: 在resultMap 使用accociation标签设置javaType=”级连属性所对应的POJO”
方法3:在resultMap 使用accociation标签设置属性select=”得到级连属性POJO的方法”
column=”方法的参数(通常是外键所对应的列)”
表的结构:
mysql> desc user;
+————+————-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————+————-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| password | varchar(20) | YES | | NULL | |
| address_id | int(10) | YES | MUL | NULL | |
+————+————-+——+—–+———+—————-+
4 rows in set (0.00 sec)
mysql> desc address;
+——-+————-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——-+————-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| city | varchar(30) | YES | | NULL | |
+——-+————-+——+—–+———+—————-+
建表语句
create table user
(
id int auto_increment
primary key,
name varchar(20) null,
password varchar(20) null,
address_id int(10) null,
constraint user_ibfk_1
foreign key (address_id) references address (id)
);
create table address
(
id int auto_increment
primary key,
city varchar(30) null
);
<!--查询一个复杂的关系 涉及到两张表
可以通过在resultMap中 使用级连属性完成映射
-->
<!--select u.id id ,u.name name,u.password password,u.address_id aid,a.city city from user u,address a where a.id = u.address_id and u.id = 1;-->
<resultMap id="userMapPlus" type="model.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<!--以下两条属性为级连属性-->
<!--方法1-->
<!--<result column="aid" property="address.id"/>-->
<!--<result column="city" property="address.city"/>-->
<!--方法2-->
<association property="address" javaType="model.Address">
<id column="aid" property="id"/>
<result column="city" property="city"/>
</association>
</resultMap>
<select id="getUserPlusById" resultMap="userMapPlus">
select u.id id ,u.name name,u.password password,u.address_id aid,a.city city
from user u,address a
where a.id = u.address_id and u.id = #{id}
</select>
<!--方法3
通过association 进行分步查询 先根据id在user表中查出来 * ->User.properties
然后在根据address_id 在address表中查出来id city ->User.Address
最后合成为一个 返回
-->
<resultMap id="associationMapPlus" type="model.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<!--通过association 告诉address这个属性是通过Dao.AddressMapper.getAddressById
这条语句查询出来的
我们需要传入 address_id 作为Dao.AddressMapper.getAddressById的id
通过column来传入值
存在问题:
当我们每次获取user的时候 都会去查询address属性 即使我们不使用address 这必然
会造成数据库资源的浪费 影响性能。
解决办法:
使用延迟加载:
在配置文件中添加
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
-->
<association property="address"
select="Dao.AddressMapper.getAddressById"
column="address_id"/>
</resultMap>
<select id="getUserPlusInAssociationById" resultMap="associationMapPlus">
select * from user where id = #{id}
</select>
resultMap对于集合属性的处理
<resultMap id="MyAddressPlus" type="model.Address">
<id column="id" property="id"/>
<result column="city" property="city"/>
<!--集合属性多使用
collection定义集合属性
ofType指定集合内多属性元素类型
对应指定操作对象 我们只需要操作集合内多一个元素即可
-->
<collection property="users" ofType="model.User">
<id column="uid" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
</collection>
</resultMap>
<select id="getAddressPlusById" resultMap="MyAddressPlus">
select address_id id,city,name,password,user.id uid
from address
left join user on address.id = user.address_id where address.id=#{id};
</select>
查询语句参数传递规则
单个参数
### 多个参数
Usermapperx.xml中的代码
```xml
<select id="selectByNameAndPassword" resultType="user">
select * from user where name = #{name} and password = #{password}
</select>
直接使用参数名称 会引发异常
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
……..
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:195)
at org.apache.ibatis.reflection.wrapper.MapWrapper.get(MapWrapper.java:45)
at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122)
at org.apache.ibatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:219)
at org.apache.ibatis.executor.CachingExecutor.createCacheKey(CachingExecutor.java:146)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:82)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
... 45 more
直接使用#{name} and password = #{password} 会出现错误 引发异常
异常中提示Available parameters are [0, 1, param1, param2]
原因:对于多个参数Mybatis会做特殊处理将传入的value转为map key的值为param1~paramN
**value的值便是我们传入的name,password的值 **
解决办法1:
通过#{param1} #{param2} 来获取name password的值
解决办法2:
通过在映射的接口方法的参数上添加@Param(“name”) 、@Param(“password”) 这样就会为上述提到的map指定名称。然后就可以直接通过名称获取了
POJO参数
直接通过#{属性名} 即可使用
自定义map
可以自己编写一个map作为查询参数 书写语句的时候直接使用#{name} and password = #{password} 即可
集合类型的参数
如果是集合类型的参数 也会被mybatis 封装成map
Key: Collection ——> collection
list ——>list
array. ——>array
比如public User getUserById(List
若要取出第一个id的值 使用 #{list[0]}
Mybatis参数Mapper ,@Param(“”)底层原理
查看 MapperMethod.java 源代码
Object param = method.convertArgsToSqlCommandParam(args);
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
ParamNameReovler.java 源码:getNameParams函数解析
public class hah {
public Object getNamedParams(Object[] args) {
/*
* User selectByNameAndPassword(@Param("name") String name, @Param("password") String password);
* 假设我们调用selectByNameAndPassword("an","123456")
*
* names已经有值了 map {0=name,1=password}
* args 是调用方法传来的参数 [an,123456]
*
* 如何得到已经处理好的names?
* 1。获取注解将@Param的值 name password 赋值给name
* 2。每次解析一个参数给map保存信息(key:索引,value:name的值)
* name:有注解:是注解 的值
* 无注解:全局配置(jdk1。8之后) name = 参数名
* name = map.size() 相当于当前元素的索引
*
*
* */
final int paramCount = names.size();
//参数为空 直接返回
if (args == null || paramCount == 0) {
return null;
}
//无注解标注 且只有一个参数 直接返回那个值
else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
}
//对两个及以上的参数进行处理
else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
/*
* 第一步处理:
* map类型的args——->value值, 作为新的map类型的param的key ,param的value--->args[key]
* {name = args[0], password=args[1]} 在我们的例子中:{name = "an", password="123456"}
*/
param.put(entry.getValue(), args[entry.getKey()]);
/*
*第二步处理:
* 这是我们为什么可以既可以用注解对应的名称又可以用param0,parm1的原因
* 就是在map类型的param中再 写入 key = param+Num value 还是param的value--->args[key]
* 所以 param变为{name = args[0], password=args[1],param0=args[0],param1=args[1]}
* 在我们的例子中:{name = "an", password="123456",param0="an",param1="123456"}
*/
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
取出参数的方法
#{name}
可以获得参数的值或pojo的值,将sql语句预编译(参数位置的值支持预编译),preparedStatement,可以防止sql注入
丰富的规则
First, like other parts of MyBatis, parameters can specify a more specific data type.
#{property,javaType=int,jdbcType=NUMERIC}
${name}
可以获得参数的值或pojo的值,直接将参数拼接到sql语句中,可能会有安全问题
在原生jdbc不支持占位符号的时候 我们可以使用#{} 比如:order by ${name}