【Mybatis】非常重要!mybatis完成参数接收及结果集返回的多种方式

一、前言

在使用Mybatis框架时,业务层会根据实际业务需求给Dao层传入参数,Dao层在根据传入的参数做了相关增删改查操作后会返回给业务层数据(从表中查询出的数据等)。

不管是接收参数还是返回结果集,Mybatis框架为了迎合多变的业务场景都给出了多种应对方案,下面将重点阐述这些方案。

二、Mybatis如何接收参数

方式一通过实体对象接收

在前几节的内容中,大部分的示例采用的都是这种方式,下面进行一个示例回顾。

Dao层接口UserMapper增加selectByPojo方法。

public User selectByPojo(User user);

可以发现Dao层的selectByPojo的参数类型是User类型,也就是通过实体对象接收参数。

映射文件UserMapper.xml中增加

<select id="selectByPojo" parameterType="com.zssj.domain.User" resultType="com.zssj.domain.User">
       select * from user where id = #{id} and username = #{username} and password = #{password}
</select>

测试方法如下

     @Test
    public void test17() throws IOException {

        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = new User(2, "lisi", "hello123");
        User userRes = userMapper.selectByPojo(user);
        System.out.println(userRes);
        //6. 释放资源
        session.close();
        in.close();
    }

在测试方法中我们生成了id为2,username为lisi,password为hello123的对象。

再用Dao层userMapper调用selectByPojo方法,并将该对象作为参数传入。

UserMapper.xml对应的select标签用parameterType接收该实体对象并在SQL语句中采用#{对象属性值}的方式即#{id}、#{username}、#{password}读取user对象中的属性值。

这种方式是比较常见的参数接收方式,其可以将SQL语句所需的多个参数通过一个对象全部接收。

方式二通过多个参数接收

如上述通过实体对象接收参数,如果我是分别通过多个参数传入Dao层,Mybatis该如何获取参数呢?下面通过实例讲解。

Dao层接口UserMapper增加selectByPararms方法

public User selectByPararms(int id, String username, String password);

映射文件UserMapper.xml中增加

<select id="selectByPararms" resultType="com.zssj.domain.User">
        select * from user where id = #{id} and username = #{username} and password = #{password}
</select>

测试方法如下

 @Test
    public void test18() throws IOException {

        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User userRes = userMapper.selectByPararms(2,"lisi","hello123");
        System.out.println(userRes);

        //6. 释放资源
        session.close();
        in.close();
    }

在测试方法中我们在用userMapper调用selectByPararms方法时传入的参数并没有封装到实体对象中传递过去,而是直接将对应的实参传递过去。此时我们运行测试方法发现报了如下错误。

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

这个报错显示没有找到id的值,也就是Dao层参数传递给UserMapper.xml的select标签时没有做好参数对应。

此时在多参数参与传递时要特别注意需要借助注解@Param,将Dao层接口UserMapper中selectByPararms每个参数都配有该注解就能正常执行了。

@Param()这个注解其实起到了类似于取别名的作用,这样mybatis就能根据这个注解将参数实现一一对应,不会出现多个参数但是值对应不上的情况。

方式三通过Map接收

Dao层接口UserMapper增加selectByMap方法

 public User selectByMap(Map<String,Object> map);

映射文件UserMapper.xml中增加

<select id="selectByMap" resultType="com.zssj.domain.User">
      select * from user where id = #{id} and username = #{username} and password = #{password}
</select>

测试方法如下

@Test
    public void test19() throws IOException {

        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("id",2);
        map.put("username", "lisi");
        map.put("password","hello123");
        User userRes = userMapper.selectByMap(map);
        System.out.println(userRes);
        //6. 释放资源
        session.close();
        in.close();
    }

在测试方法中生成map并往里面塞值,key作为属性名,value是对应的值。

在UserMapper.xml对应的select标签中直接采用#{id}、#{username}、#{password}方式取出map中对应的值参与sql。

方式四#与$的区别

在上述示例中我们都采用#{属性名称}的方式获取变量值,除此之外也可以将#替换成$。

我们对通过Map接收这个示例的代码进行修改,将UserMapper.xm对应的selesct标签修改为使用$获取变量值。

<select id="selectByMap" resultType="com.zssj.domain.User">
                select * from user where
                 id = ${id} and username = ${username} and password = ${password}
    </select>

然后运行测试方法却发现报错

### The error may exist in com/zssj/mapper/UserMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select * from user where id = 2 and username = lisi and password = hello123
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'lisi' in 'where clause'

通过分析错误展现的sql语句乍一看并没有错,但是我发现username对应的lisi和password对应的hello123本应该是字符串类型,这里并没有体现出来。

于是我改写了测试方法方法

@Test
    public void test19() throws IOException {

        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("id",2);
        map.put("username", "'"+"lisi"+"'");
        map.put("password","'"+"hello123"+"'");
        User userRes = userMapper.selectByMap(map);
        System.out.println(userRes);
        //6. 释放资源
        session.close();
        in.close();
    }

在测试方法中,将字符串类型的username和password对应的值都加上了单引号('')。此时再进行测试,就成功了。

究其原因使用$取值时其仅仅是采用字符串拼接的形式,这也是和#不同之处,#采用占位符的形式。

另外还要强调一点的是,$取值还有个特殊的用处,如果sql语句中表名是变化的,那就只能用$取值。

除此之外,group by 字段 ,order by 字段,字段名等没法使用占位符的就需要使用${}形式。

三、Mybatis如何返回结果集

方式一利用resultType结果集返回List集合

Dao层接口UserMapper增加findAll方法

public List<User> findAll();

映射文件UserMapper.xml中增加

<select id="findAll" resultType="com.zssj.domain.User">
     select * from user
</select>

测试方法如下

@Test
    public void test1() throws IOException {

        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        List<User> userList = userMapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
        //5. 释放资源
        session.close();
        in.close();
    }

在测试方法中userMapper调用findAll方法,查询出数据库中的所有数据。

可以看到Dao层的findAll方法的返回值是List<User>集合,那么在UserMapper.xml对应的select标签使用结果集元素resultType时,将其值设为User类的全限定类名即可,那么查询出的所有数据都会被封装到该集合中。

方式二利用resultType结果集返回Map集合

Dao层接口UserMapper增加selectUsersByMap方法

     @MapKey("id")
    public Map<String, User> selectUsersByMap();

映射文件UserMapper.xml中增加

<select id="selectUsersByMap" resultType="com.zssj.domain.User">
        select * from user
  </select>

测试方法如下

@Test
    public void test21() throws IOException {
        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        Map map = userMapper.selectUsersByMap();
        //6. 释放资源
        session.close();
        in.close();
    }

测试结果如下

{1=User{id=1, username='liuxiang', password='hello789', birthday=null}, 
 2=User{id=2, username='lisi', password='hello123', birthday=null}, 
 3=User{id=3, username='wangwu', password='hello123', birthday=null}, 
 4=User{id=4, username='zhaoyun', password='hello123', birthday=null}, 
 5=User{id=5, username='machao', password='hello123', birthday=null}}

通过结果可以知道,返回的结果集Map集合中key是每个User对象的id值,value是User对象。

当然key的值可以通过Dao层接口UserMapper对应方法的 @MapKey("")注解进行自定义,选取对象中的某个字段作为key就将这个字段的属性名放入注解即可。

值得一提的是select标签中的resultType元素仍然是User实体类的全限定类名。

方式三利用resultMap完成基本属性值封装

上述介绍了使用resultType将数据库中的结果自动封装到Java实体对象中,但是有些时候这种自动封装不能满足很多现实场景的需要。

比如数据库字段和Java实体类中的字段对应不上,因此值也就封装不起来,比如数据库的字段是user_name,而Java实体类中的属性名是username。

这个时候就需要借助resultMap,具体代码示例如下。

Dao层接口UserMapper增加selectUserByResultMap方法

public User selectUserByResultMap(Integer id);

映射文件UserMapper.xml中增加

<resultMap id="userResult" type="com.zssj.domain.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <resultcolumn="password" property="password"/>
        <resultcolumn="mail_code" property="mailCode"/>
  </resultMap> 
<select id="selectUserByResultMap" resultMap="userResult">
        select * from user where id = #{id}
 </select>

测试方法如下

    @Test
    public void test22() throws IOException {
        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.selectUserByResultMap(1);
        System.out.println(user);
        //6. 释放资源
        session.close();
        in.close();
    }

可以发现在UserMapper.xml对应的resultMap标签中id(一般设置主键字段)及result对应的是数据库查询出的字段名,而property对应的是java实体对象中的属性名称。

在上述完成了一一对应后,将对应规则赋给select标签的resultMap即可。

方式四resultMap完成一对一封装

所谓高级映射就是在java实体类中包含另一个实体类,在这种情况下myabtis是如何借助resultMap完成结果集封装的呢?

为了介绍此种情况,需要增加新的实体类Department。

public class Department {
    private Integer id;
    private String departmentName;
    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 + '\'' +
                '}';
    }
}

并且将该实体类也作为User类的属性元素即private Department department。

public class User {
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private String mailCode;
    private Department department;
    public User() {
    }
    public User( String username, String password) {
        this.username = username;
        this.password = password;
    }
    public User( int id,String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
    public User( int id,String username, String password, Date birthday,String mailCode) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.birthday = birthday;
        this.mailCode = mailCode;
    }
    public Department getDepartment() {
        return department;
    }
    public void setDepartment(Department department) {
        this.department = department;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public String getMailCode() {
        return mailCode;
    }
    public void setMailCode(String mailCode) {
        this.mailCode = mailCode;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", birthday=" + birthday +
                ", mailCode='" + mailCode + '\'' +
                ", department=" + department +
                '}';
    }
}

如上所示,Department类型也作为User类的属性元素,这个时候通过下述示例代码介绍封装方法。

Dao层接口UserMapper增加getUserAndDept方法

public User getUserAndDept(Integer id);

映射文件UserMapper.xml中增加

<!--resultMap高级映射 级联属性   -->
    <resultMap id="userResult2" type="com.zssj.domain.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="birthday" property="birthday"/>
        <result column="mail_code" property="mailCode"/>
        <result column="did" property="department.id"/>
        <result column="dept_name" property="department.departmentName"/>
    </resultMap>
    <select id="getUserAndDept" resultMap="userResult2">
        select u.id id, u.username username, u.password password,
         u.birthday birthday,u.mail_code mail_code,u.dept_id dept_id,
         d.id  did, d.dept_name dept_name
         from user u inner join tbl_dept d on u.dept_id = d.id where u.id = #{id}
    </select>

在上述代码的select标签里依然用resultMap指定封装结果集的方式。

由于返回结果集是User类型的,而在User类型中有个引用数据类型Department,我们发现可以采用department.id、department.departmentName这种级联的方式将数据库中对应的字段值赋给Department的属性。

测试方法如下

@Test
    public void test23() throws IOException {
        //1. 读取核心配置文件SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3. 使用工厂生产一个SqlSession对象
        SqlSession session = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.getUserAndDept(16);
        System.out.println(user);
        //6. 释放资源
        session.close();
        in.close();
    }

在测试方法中调用getUserAndDept方法并传入参数员工的id值,根据此id查询员工信息和部门信息。

除了上述级联的方式外,还可以采用子标签association来完成这一封装。

那么只要将映射文件UserMapper.xml中对应的resultMap标签改为

<resultMap id="userResult3" type="com.zssj.domain.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="birthday" property="birthday"/>
        <result column="mail_code" property="mailCode"/>
        <association property="department" javaType="com.zssj.domain.Department">
            <result column="did" property="id"/>
            <result column="dept_name" property="departmentName"/>
        </association>
    </resultMap>

我们发现将Department类型的属性值放入association标签中,并且与数据库字段值一一对应即可。

当然association子标签中javaType类型是Department类型。

方式五一对多查询

所谓一对多查询就是在一个实体对象中包含集合属性,假设在部门实体类中加入属性private List<User> userList,即模拟一个部门中可以有很多员工。

这样可以满足需求在查询部门时将其所有员工悉数封装到属性userList中,那么具体在封装过程中,mybatis框架是如何实现的呢?答案还是使用resultMap。

Dao层接口DepartmentMapper增加方法getDeptByIdPlus

public Department getDeptByIdPlus(Integer id);

与其对应的DepartmentMapper.xml中的select标签为

<resultMap id="myDept" type="com.zssj.domain.Department">
        <id column="did" property="id"/>
        <result column="departName" property="departmentName"/>
        <collection property="userList" ofType="com.zssj.domain.User">
            <id column="uid" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="birthday" property="birthday"/>
            <result column="mail_code" property="mailCode"/>
        </collection>
    </resultMap>
    <select id="getDeptByIdPlus" resultMap="myDept">
        select d.id did, d.dept_name departName, u.id uid, u.username username,
               u.password password, u.birthday birthday, u.mail_code mail_code
        from tbl_dept d left join user u on d.id = u.dept_id
        where d.id = #{id}
    </select>

在select标签中采用表连接的方式分别查出user表(用户表)和tbl_dept(部门表)的相关信息,业务需求则是通过部门id查出此部门的全部员工。

在resultMap中部门实体类中的id属性和departmentName属性可以实现简单的封装即可。

但是属性userList是集合类型,因此需要借助collection子标签,其中property对应的就是该属性名,而ofType对应的是集合中实体对象的全限定类名,这里对应的是User类。

在collection标签中完成User对象的属性值封装即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值