Mybatis增删查改Mapper参数及部分源码分析


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 selectByIdReturnMap(Integer id);

##### 返回map类型 每个map封装好一条行记录 key是列名 value是记录值
Map selectByIdToMap(Integer id);



可以通过设置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>

查询语句参数传递规则

单个参数

``` name随便写什么都可以 Mybatis不会做特殊处理
### 多个参数 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 ids);

​ 若要取出第一个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}


文章作者: Bxan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bxan !
  目录