5、MyBatis中的输入参数映射(@Param、Map、@MapKey)、输出参数映射resultMap标签、mapUnderscoreToCamelCase(单表下的演示)


Mapper代理开发模式,编程者只需要编写三项内容:

model 实体类,通常一个程序只编写一次,可以编写工具一键生成。

mapper接口,Java

mapper映射,xml

mapper.xml,这个就是MyBatis编程核心关注的文件,尤其是mapper映射中的查询。

在mapper.xml映射文件中,需要 查找sql,参数映射,执行sql,结果解析。

再解输入和输出映射

在MyBatis中提供了两组输入和输出映射支持方式

输入参数映射:parameterType="" parameterMap=""
输出参数映射:resultType="" resultMap=""

在前边的示例中,我们发现parameterType可以无需指定,无论参数是对象(插入、更新)类型,还是基本数据类型(删除Integer、根据id查找、根据姓名模糊查找)。以上没有编写parameterType都是因为我们的参数是单个,在实际开发中,我们通常参数有多个,比如分页查询,pageIndex,pageSize。

建立模块mybatis-03

输入参数映射

多个输入参数

如果有多个参数,MyBatis如何处理?

案例:分页查询

package org.westos.mapper;

import org.westos.model.Account;

import java.util.List;

/**
 * @author lwj
 * @date 2020/9/5 14:00
 */
public interface AccountMapper {
    List<Account> queryAllAccount();

    //从第几条数据开始,查pageSize条数据,因为如果按页查询,需要 (pageIndex - 1) * pageSize
    List<Account> select(Integer startNo, Integer pageSize);
}
<?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="org.westos.mapper.AccountMapper">
    <select id="queryAllAccount" resultType="Account" >
        select aid, aname, apassword, a_nickname anickname
        from account
    </select>
    
    <select id="select" resultType="Account">
        select
               aid, aname, apassword, a_nickname anickname
        from
             account
        limit
            #{startNo}, #{pageSize}
    </select>
</mapper>

测试:

@Test
public void testSelect() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int pageIndex = 1;
        int pageSize = 3;
        List<Account> list = mapper.select((pageIndex - 1) * pageSize, pageSize);
        System.out.println(JSON.toJSONString(list, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

异常信息:

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'startNo' not found. Available parameters are [arg1, arg0, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'startNo' not found. Available parameters are [arg1, arg0, param1, param2]

	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
	at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:139)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:76)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
	at com.sun.proxy.$Proxy5.select(Unknown Source)
	at org.westos.mapper.AccountMapperTest.testSelect(AccountMapperTest.java:36)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'startNo' not found. Available parameters are [arg1, arg0, param1, param2]
	at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:204)
	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)
	... 28 more

以上异常的详细信息,是说MyBatis会将多个参数,以Map的方式进行处理,在mapper.xml文件中引用参数的时候,必须使用

Available parameters are [arg1, arg0, param1, param2]
再有很多继续向后编

正确的写法:

<select id="select" resultType="Account">
    select
    aid, aname, apassword, a_nickname anickname
    from
    account
    limit
    #{param1}, #{param2}
</select>

以上代码的可读性很差,如果是一个复杂查询,则不知道参数都是什么意义。

MyBatis允许我们使用注解@Param对参数进行命名。

List<Account> select(@Param("startNo") Integer startNo, @Param("pageSize") Integer pageSize);
<select id="select" resultType="Account">
    select
    aid, aname, apassword, a_nickname anickname
    from
    account
    limit
    #{startNo}, #{pageSize}
</select>

以上情况适用的场景就是:参数不是很多,而且不是一个对象类型

如果参数超过3个,可以使用Map作为参数或者直接编写一个对象封装参数。

/**
     * 当使用Map作为参数时,必须要写方法的注释(传递的参数)
     * @param map startNo 从第几条数据开始,pageSize 每页的大小
     * @return
     */
List<Account> selectByMap(Map<String,Object> map);
<select id="selectByMap" resultType="Account">
    select
    aid, aname, apassword, a_nickname anickname
    from
    account
    limit
    #{startNo}, #{pageSize}
</select>

测试:

@Test
public void testSelectByMap() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int pageIndex = 1;
        int pageSize = 3;
        Map<String,Object> map = new HashMap<>();
        map.put("startNo", (pageIndex - 1) * pageSize);
        map.put("pageSize", pageSize);
        List<Account> list = mapper.selectByMap(map);
        System.out.println(JSON.toJSONString(list, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

如果对象的属性和数据库的列名不一致,MyBatis如何处理?

注意:在mapper.xml映射文件,sql中的表达式(#{anickname}、#{aname}),都是对象的属性对应的get方法名称,与属性名称无关

(Spring框架中允许直接对私有属性注入)

void insertAccount(Account account);
<insert id="insertAccount">
    insert into
    account(aid, aname, apassword, a_nickname)
    values (#{aid}, #{aname}, #{apassword}, #{anickname})
</insert>

注意:

唯一需要注意的是,设计表之后,尽量不要修改列名,要修改,则整个model实体类和mapper映射文件都要改。

还要注意:后面使用生成代码的方式,一旦代码生成,不要修改目录结构,因为引用关系之前已经设置好了。

属性和方法区分:
在mybatis的映射文件中,表达式中所编写的内容在查找的时候,是通过方法关联,而非属性关联。
当然绝大部分情况,属性和方法是一致的。

private String aloginname;
public void setAname(String aname) {
	this.aloginname = aname;
}
在mybatis中表达式
#{aname}是可以获取数据的


private String aloginname;
public void setAloginname(String aname) {
	this.aloginname = aname;
}
#{aname} 当查询参数是对象时,会直接报错,因为OGNL无法从根对象中获取对应的方法得到值,there is no getter for column 'aname'

VO

Value Object

如果查询条件复杂,查询条件来源于多个model如何处理?

MyBatis支持从封装类中再次读取引用对象的属性值

package org.westos.search;

import lombok.Data;
import org.westos.model.Account;

/**
 * 查询条件的封装类
 * 因为查询条件来源于多个封装类
 * @author lwj
 * @date 2020/9/7 11:28
 */
@Data
public class QuerySearchVO {
    private Account account;
//    private Order order;
//    查询条件来自多个实体类
}
/**
     * 当查询的条件来自多个model实体类时,可以使用@Param,可以使用Map封装,也可以使用下面的这种方式,VO封装
     * @param vo
     * @return
     */
List<Account> selectByQuerySearchVO(QuerySearchVO vo);

MyBatis通过对象图导航语言,OGNL表达式可以获取根对象的值,(和EL类似)

<select id="selectByQuerySearchVO" resultType="Account" parameterType="org.westos.search.QuerySearchVO">
    select
    aid, aname, apassword, a_nickname anickname
    from
    account
    where aname like #{account.aname}
</select>

测试:

@Test
public void testSelectByVO() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = new Account();
        account.setAname("zhangsan");
        QuerySearchVO querySearchVO = new QuerySearchVO();
        querySearchVO.setAccount(account);
        List<Account> list = mapper.selectByQuerySearchVO(querySearchVO);
        System.out.println(JSON.toJSONString(list, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

注意:所有的select标签,都需要指定resultType或者resultMap,因为MyBatis要进行输出参数的映射。

(parameterMap不推荐使用了)

输入参数小结

小结输入参数映射:

实际开发中,输入参数类型大部分都不需要指定。

参数可以使用基本数据类型、引用数据类型。

一个参数无需额外配置;参数如果有多个,建议两个、三个参数用@Param命名方式,超过3个用Map或者对象封装。

复杂查询sql的条件来源于多个model实体类,可以使用包装查询条件类作为参数,OGNL也可以解析。

输出参数映射

输出参数映射首先必须要指定,int、对象、Map。

int和对象类型在之前已经练习过。

<update id="updateAccount" parameterType="java.lang.Integer">
    update account
    set aname = #{aname}, apassword = #{apassword}, a_nickname = #{anickname}
    where aid = #{aid}
</update>
<select id="queryByPrimaryId" resultType="Account">
    select aid, aname, apassword, a_nickname as anickname
    from account
    where aid = #{aid}
</select>

Map返回值映射

/**
     * 返回值类型为Map
     * @param aid
     * @return
     */
Map<String,Object> query(int aid);
<select id="query" resultType="map">
    select
    aid, aname, apassword, a_nickname
    from
    account
    where aid = #{aid}
</select>

测试:

@Test
public void testQuery() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Map<String, Object> map = mapper.query(1);
        System.out.println(JSON.toJSONString(map, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
{
	"a_nickname":"张三",
	"aname":"zhangsan",
	"apassword":"nasgnahz",
	"aid":1
}

@MapKey

mybatis还支持结果查询为map,可以指定某列为map的key,值为对象。

@MapKey("aname")
Map<String,Account> query2(int aid);
<select id="query2" resultType="map">
    select
    aid, aname, apassword, a_nickname as anickname
    from
    account
    where aid = #{aid}
</select>

测试

@Test
public void testQuery2() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Map<String, Account> map = mapper.query2(1);
        System.out.println(JSON.toJSONString(map, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
{
	"zhangsan":{
		"aname":"zhangsan",
		"apassword":"nasgnahz",
		"anickname":"张三",
		"aid":1
	}
}

如果查询的列和对象的属性不一致?引出ResultMap登场。

ResultMap

resultMap可以做resultType能做的事情,还可以做的更多。

<!--统一写在上方-->
<!--自定义结果类型转换-->
<!--orm的m是mapping,类对应表,属性对应列-->
<resultMap id="resultMapAccount" type="org.westos.model.Account">
    <!--主键比较特殊,需要使用id单独配置-->
    <id property="aid" column="aid"/>
    <!--属性和表的列的对应关系-->
    <result property="anickname" column="a_nickname"/>
    <result property="aname" column="aname"/>
    <result property="apassword" column="apassword"/>
</resultMap>
<!--resultMap解决列名和属性无法自动映射-->
<!--resultMap是mybatis的一个标签,表示结果集的映射配置-->
<select id="selectByPrimaryKey" resultMap="resultMapAccount">
    select
    aid, aname, apassword, a_nickname
    from
    account
    where aid = #{aid}
</select>
Account selectByPrimaryKey(Integer aid);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account implements Serializable {
    private static final long serialVersionUID = 6623184271851977540L;
    private Integer aid;
    private String aname;
    private String apassword;
    private String anickname;
}

测试:

@Test
public void testSelectByPrimaryKey() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.selectByPrimaryKey(1);
        System.out.println(JSON.toJSONString(account, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
{
	"aid":1,
	"aname":"zhangsan",
	"anickname":"张三",
	"apassword":"nasgnahz"
}

列名和属性一致的情况下,可以使用automapping。

<!--autoMapping, 自动映射,规则就是列和属性名要一致-->
<!--type,最终结果属于什么类型,封装到哪一个类中-->
<resultMap id="resultMapAccount2" type="org.westos.model.Account" autoMapping="true">
    <id property="aid" column="aid"/>
    <result property="anickname" column="a_nickname"/>
</resultMap>
<resultMap id="resultMapAccount2" type="org.westos.model.Account" autoMapping="true">
    <id property="aid" column="aid"/>
    <result property="anickname" column="a_nickname"/>
</resultMap>

id可以去掉。

<select id="selectByPrimaryKey" resultMap="resultMapAccount2">
    select
    aid, aname, apassword, a_nickname
    from
    account
    where aid = #{aid}
</select>

mapUnderscoreToCamelCase

补充说明:

数据库的列名和Java的属性名,尽量第一次创建之后,保持不变。

数据库如果列名有下划线,Java属性没有下划线;

a_nickname,anickname

前面我们通过查询时候的as别名,通过resultMap设置对应关系。

除了上面两种方式,mybatis还支持全局的配置,去除下划线。

<settings>
    <!-- 忽略下划线 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
Account selectByPrimaryId(int aid);
<select id="selectByPrimaryId" resultType="Account">
    select
    aid, aname, apassword, a_nickname
    from
    account
    where aid = #{aid}
</select>
@Test
public void testSelectByPrimaryId() {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.selectByPrimaryId(1);
        System.out.println(JSON.toJSONString(account, true));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
{
	"aid":1,
	"aname":"zhangsan",
	"anickname":"张三",
	"apassword":"nasgnahz"
}

注意:

当参数是对象时,#{}、${}中填写的内容是对象的属性(get属性名),区分大小写。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 的 @MapKey 注解和 resultMap 的 <map> 标签都用于将查询结果转换为 Map 类型,但它们的使用方式略有不同。 @MapKey 注解用于将查询结果集的某一列作为 Map 的键,将整个结果集放入 Map 返回。它通常与 selectMap() 方法一起使用,示例如下: ```java @MapKey("id") Map<Integer, User> selectUserMap(); ``` 上述代码,@MapKey("id") 注解指定将查询结果集的 id 列作为 Map 的键,将整个结果集转换为 Map<Integer, User> 类型返回。 而 <map> 标签则用于将查询结果集的多列转换为一个 Map 类型的属性,通常用于一对多关系的映射。示例如下: ```xml <resultMap id="orderMap" type="Order"> <id property="id" column="id"/> <result property="orderNo" column="order_no"/> <result property="createTime" column="create_time"/> <collection property="orderItems" ofType="OrderItem"> <id property="id" column="item_id"/> <result property="name" column="item_name"/> <result property="quantity" column="item_quantity"/> </collection> <map property="extra" columnPrefix="extra_"> <key column="name"/> <value column="value"/> </map> </resultMap> ``` 上述代码,<map> 标签用于将查询结果集以 extra_ 前缀开头的多列转换为一个 Map<String, Object> 类型的属性,其 name 列作为 Map 的键,value 列作为 Map 的值。 需要注意的是,@MapKey 注解和 <map> 标签都需要指定一个属性作为 Map 的键,如果没有指定,则默认将整个查询结果集转换为 Map 类型返回。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值