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属性名),区分大小写。