MyBatis

Mybatis上手教程–select语句的resultType相关配置

一返回结果是List时resultType的配置

通常,我们的查询会查出多条记录来,这样就封装到List中。那么此时,我们的SQL mapper文件中,该怎么配置呢?

1 接口文件
package com.knockatdatabase.dao;

import com.knockatdatabase.Employee;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

/**
* @Author:asher
* @Date:2021/10/2 10:24
* @Description:com.knockatdatabase.dao
* @Version:1.0
*/
public interface EmployeeMapper {
   Employee getEmployeeById(Integer id);

   void insertEmployee(Employee employee);

   Boolean updateEmployee(Employee employee);

   Boolean deleteEmployeeById(Integer id);

   Employee getEmployeeByIdAndName(@Param("id11") Integer id, @Param("lastName") String name);

   Employee getEmployeeByMap(Map<String, String> map);

   List<Employee> getEmployeesByEmail(String email);

}

最后1行的代码即是我们想要的定义的方法。 List<Employee> getEmployeesByEmail(String email);

2 SQL mapper
<?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="com.knockatdatabase.dao.EmployeeMapper">
<insert id="insertEmployee" parameterType="com.knockatdatabase.Employee" useGeneratedKeys="true" keyProperty="id">
   insert into tbl_employee(last_name,gender,email)
   values (#{lastName},#{gender},#{mail})
</insert>
   <update id="updateEmployee">
       update tbl_employee set last_name=#{lastName},gender=#{gender},email=#{mail}
       where id=#{id}
  </update>
   <delete id="deleteEmployeeById">
       delete from tbl_employee where id=#{id}
  </delete>
   <select id="getEmployeeById" resultType="com.knockatdatabase.Employee">
   select id,last_name ,gender,email as mail from tbl_employee where id = #{id}
</select>
   <select id="getEmployeeByIdAndName" resultType="com.knockatdatabase.Employee">
       select id,last_name,gender,email as mail from tbl_employee where id=#{id11} and last_name=#{lastName}
  </select>
   <select id="getEmployeeByMap" resultType="com.knockatdatabase.Employee">
       select id,last_name,gender,email as mail from tbl_employee
       where id=#{id} and last_name=#{name}
  </select>
   <select id="getEmployeesByEmail" resultType="com.knockatdatabase.Employee">
       select * from tbl_employee where email like #{email}
  </select>
</mapper>

这里的getEmployeesByEmail便是我们想要的映射文件。这里要尤其注意:like后面的写法:依然要写#{email},当然,这里的参数名可以随便写,比如:#{abcd12345}也行。为什么呢?这个在Mybatis的源码里可以找到答案,当参数是1个参数的时候,参数名可以随便写。当然,我现在暂时还没有去读Mybatis的关于参数部分的源代码。暂时先记下来吧。

另外:返回值类型,记得同样要写List集合里存放的对象的类型,而不是List本身。所以,resultType依然要写对应的Java bean本身的类型,Employee 这一点,官方文档「https://mybatis.org/mybatis-3/sqlmap-xml.html」说的很清楚:

resultType:The fully qualified class name or alias for the expected type that will be returned from this statement. Note that in the case of collections, this should be the type that the collection contains, not the type of the collection itself. Use resultType OR resultMap, not both.

3 测试Java代码
@Test
   public void testGetEmployeesByEmail() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           List<Employee> employeesByEmail = mapper.getEmployeesByEmail("%163%");
           for (Employee e: employeesByEmail ) {
               System.out.println(e);
          }
      } catch (IOException e) {
           e.printStackTrace();
      }
       finally {
           sqlSession.close();
      }
  }
//执行结果:
Employee{id=2, lastName='Huang', gender='?', mail='null'}
Employee{id=4, lastName='Huang', gender='男', mail='null'}
Employee{id=5, lastName='Huang', gender='男', mail='null'}
Employee{id=6, lastName='Huang', gender='男', mail='null'}
Employee{id=7, lastName='Huang', gender='男', mail='null'}
Employee{id=8, lastName='Huang', gender='男', mail='null'}

注意:这里的Mybatis会把查询出来的结果自动封装到List中去,不需要我们去干预。至于说此时查询结果中mail=’null’的原因,是我们写的select * from …而此时数据库中表字段名为email,而Java bean中的属性名却是mail。解决问题的办法是前面我们已经使用过的,该写列别名的方式,或者是后面,我们将要学习使用的resultMap标签属性。这里,先就这样。

4 错误案例

处于DBA的职业习惯,太习惯于直接写SQL语句的原因,导致,我在前面的SQL mapper文件中,写成了下面的错误代码:

   <select id="getEmployeesByEmail" resultType="com.knockatdatabase.Employee">
       select * from tbl_employee where email like '#{email}'
  </select>

select * from tbl_employee where email like ‘#{email}’,把SQL语句的参数放到一对儿单引号里面了,然后误以为将来Java代码里传递过来的参数%163%正好放到单引号里,那么SQL语句就是:select * from tbl_employee where email like ‘%163%’;当然,这是错误的。这是我错误的以为了Mybatis的处理方式。

那么,这时,我的测试代码将会产生下述错误:

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='email', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
### The error may exist in EmployeeMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select * from tbl_employee where email like '?'
### Cause: org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='email', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).

看起来是个非常莫名其妙的报错!

Google搜索一下,有[其他人遇到这个错误,参考别人的报错代码]  https://programmerah.com/typeexception-error-setting-non-null-for-parameter-1-with-jdbctype-null-24403/ 

,才发现我自己这里的写法错误,把单引号去掉就OK啦。

注意:当返回值是一个集合List时,resultType要指定集合中盛装对象的类型,即写Java bean对应的实体类,而不是写java.util.List。

二 返回结果是Map时resultType的配置

有时候,我们的返回结果希望是一个map,而不希望是一个实体类的对象。返回的map中,key就是数据库中表的字段名,value就是库表中字段对应的值。

1 EmployeeMapper接口文件,EmployeeMapper.java

方法返回值定义为Map类型,Key选择String,Value选择一个Object去接。

Map<String, Object> getEmployeeByIdReturnMap(Integer id);
2 SQL mapper文件,EmployeeMapper.xml
    <select id="getEmployeeByIdReturnMap" resultType="java.util.Map">
       select * from tbl_employee where id=#{id}
  </select>

注意,此时的resultType类型是map。

3 测试类Java代码,EmployeeTest.java
    @Test
   public void testGetEmployeeByIdReturnMap() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           Map<String, Object> mapEmployee = mapper.getEmployeeByIdReturnMap(5);
           System.out.println(mapEmployee);
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           sqlSession.close();
      }
  }
//返回结果:
{gender=男, last_name=Huang, id=5, email=hensi163@163.com}

三返回结果是Map,结果集为多条记录时resultType的配置

接上一个,如果返回值是多条记录的话,该怎么封装呢?答案是封装到List中去,多个Map对象放到List中去。

1 EmployeeMapper接口文件,EmployeeMapper.java
List<Map<String, Object>> getEmployeeByIdReturnMap(Integer id);
2 SQL mapper文件,EmployeeMapper.xml
    <select id="getEmployeeByIdReturnMap" resultType="java.util.Map">
       select * from tbl_employee where id!=#{id}
  </select>
3 测试类Java代码,EmployeeTest.java
    @Test
   public void testGetEmployeeByIdReturnMap() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           List<Map<String, Object>> mapEmployee = mapper.getEmployeeByIdReturnMap(5);
           System.out.println(mapEmployee);
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           sqlSession.close();
      }
  }
//执行结果
[{gender=男, last_name=黄伟, id=1, email=huangwei@knockatdatabase.com}, {gender=?, last_name=Huang, id=2, email=hensi163@163.com}, {gender=男, last_name=Huang, id=4, email=hensi163@163.com}, {gender=男, last_name=Huang, id=6, email=hensi163@163.com}, {gender=男, last_name=Huang, id=7, email=hensi163@163.com}, {gender=男, last_name=Huang, id=8, email=hensi163@163.com}]

此时,可以看到返回结果中,是一个List,里面包含的每一个元素的类型是一个Map对象。

当然,如果我们把SQL mapper文件中的resultType改为Java bean的实体类Employee。

    <select id="getEmployeeByIdReturnMap" resultType="com.knockatdatabase.Employee">
       select * from tbl_employee where id!=#{id}
  </select>

则返回测试结果为:

[Employee{id=1, lastName='黄伟', gender='男', mail='null'}, Employee{id=2, lastName='Huang', gender='?', mail='null'}, Employee{id=4, lastName='Huang', gender='男', mail='null'}, Employee{id=6, lastName='Huang', gender='男', mail='null'}, Employee{id=7, lastName='Huang', gender='男', mail='null'}, Employee{id=8, lastName='Huang', gender='男', mail='null'}]

则,当前情况下,返回结果是一个大的List,List里的每一个元素又是一个Employee对象。

那么,问题来了?到底什么情况下resultType写Map,什么场景下该写Java bean的实体类对象类型呢?它们各有什么区别和优势呢?应用场景又是什么呢?

四 返回结果是Map,用主键主键当作Map的key,Employee本身当作value时resultType的配置

1 EmployeeMapper接口文件,EmployeeMapper.java
    @MapKey("id")
   Map<Integer, Employee> getEmployeeByIdReturnMapValueEmployee(Integer id);

注意,此时,要在方法签名上,使用注解@MapKey(“作为map主键的表字段名”)

2 SQL mapper文件,EmployeeMapper.xml
    <select id="getEmployeeByIdReturnMapValueEmployee" resultType="com.knockatdatabase.Employee">
       select * from tbl_employee where id>#{id}
  </select>
3 测试类Java代码,EmployeeTest.java
    @Test
   public void testGetEmployeeByIdReturnMapValueEmployee() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           Map<Integer, Employee> employeeByIdReturnMapValueEmployee = mapper.getEmployeeByIdReturnMapValueEmployee(5);
           System.out.println(employeeByIdReturnMapValueEmployee);
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           sqlSession.close();
      }
  }
//结果:
{6=Employee{id=6, lastName='Huang', gender='男', mail='null'}, 7=Employee{id=7, lastName='Huang', gender='男', mail='null'}, 8=Employee{id=8, lastName='Huang', gender='男', mail='null'}, 9=Employee{id=9, lastName='Huang', gender='男', mail='null'}}

此时,可以看到输出结果是一个大的Map,Map中的每个键值对儿的key对应着数据库表中的主键字段id值,value则是对应的返回行记录,即是一个Employee对象。

需要注意的是,这里的mapper接口文件中的,方法注解上要写一个表中的唯一性字段,如果是非唯一性字段的话,则返回值就很有意思。比如:

@MapKey("gender")
Map<Integer, Employee> getEmployeeByIdReturnMapValueEmployee(Integer id);

则,返回值成了:

{男=Employee{id=9, lastName='Huang', gender='男', mail='null'}}

分析一下,是什么原因造成这个结果的呢?这是因为,当Mybatis帮我们封装的时候,采取的可能是Map的put(k,v)的方法,而我们知道,Map的put()方法返回值要么是NULL,即要put的k-v在Map中不存在;或者返回一个和当前k-v相同的key所对应的旧的value,即效果相当于是用当前的key-newvalue替换了已经存在key-oldvalue。也就是说,如果Map中已经存在{1=”one”},那么我们put(2,”two”)时,返回值为NULL;当我们put(1,”ONE”)时,则返回值为one,且此时map变成了{1=”ONE”,2=”two”}。结合当前情况下,表中的记录其gender都是“男”,所以,最后成了不停的用新的记录去覆盖旧的记录了。

留言