Mapper动态代理方式
Mapper接口开发方法只需要程序员Mapper接口(相当于dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象(相当于dao实现类),然后可以通过该代理对象进行增删改查的操作。区别是传统的编写方式dao的实现类我们需要自己创建和编写,而使用Mybatis框架的动态代理方式,我们只需要编写接口,实现类由Mabits为我们自动生成(即动态代理对象)。
Mapper接口的动态代理实现,需要遵循以下规范:
- 映射文件中的命名空间(名称控制)与Mapper接口的全路径一致
- 映射文件中的statement的Id与Mapper接口的方法名保持一致
- 映射文件中的statement的ResultType必须和mapper接口方法的返回类型一致(即使不采用动态代理,也要一致)
- 映射文件中的statement的parameterType必须和mapper接口方法的参数类型一致(不一定,该参数可省略)
测试类
public class UserMapperTest {
private UserMapper userMapper;
@Before
public void setUp() throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);//配置自动事务提交
userMapper = sqlSession.getMapper(UserMapper.class);//相当于userDao = new UserDaoImpl(sqlSession);
}
@Test
public void queryUserById() {
User user = userMapper.queryUserById(1l);
System.out.println(user);
}
}
mybatis-config.xml配置
1.加载资源文件
<properties resource="jdbc.properties"></properties>
2.开启驼峰
<settings>
<!--开启驼峰,默认是false-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
驼峰的作用
例子:
POJO类
数据库
3.配置别名
第一种方式:
<typeAliases>
<typeAlias type="test.mybatis.pojo.User" alias="User"/>
<!--自动扫描包中pojo类,将其类名当做别名-->
<package name="test.mybatis.pojo"/>
</typeAliases>
上面的方式比较麻烦我们必须一个一个配置别名所以还有第二种方式:
<typeAliases>
<!--自动扫描包中pojo类,将其类名当做别名-->
<package name="test.mybatis.pojo"/>
</typeAliases>
别名作用:
以前:
<select id="queryUserList" resultType="test.mybatis.pojo.User">
select * from tb_user
</select>
现在:
<select id="queryUserList" resultType="User">
select * from tb_user
</select>
Mapper XML 文件(映射文件)
1.Insert编写插入语句
id:插入语句的唯一标识
parameterType:插入语句的参数类型,使用动态代理之后,需要和mapper接口中的参数类型一致,可以省略。
useGeneratedKeys:开启主键自增回显(回写),将自增长的主键值回显到形参中(即封装到User对象中)
keyColumn:数据库中主键的字段名称
keyProperty:pojo中主键对应的属性
例子:
<insert id="insertUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO tb_user (
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
VALUES
(
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
now(),
now()
);
</insert>
@Test
public void testInsertUser() {
User user = new User();
user.setAge(16);
user.setBirthday(new Date());
user.setCreated(user.getBirthday());
user.setName("范爷");
user.setPassword("123");
user.setSex(1);
user.setUpdated(user.getBirthday());
user.setUserName("fanye");
userMapper.insertUser(user);
System.out.println(user.getId());
}
2.Select
Select标签:用来编写查询语句的statement
id属性:唯一标识
resultType:查询语句返回的结果集类型
parameterType:参数类型,使用动态代理之后,需要和mapper接口中的参数类型一致,可以省略。
<!--
select:编写查询语句
id:语句的唯一标识
resultType:配置返回的结果集类型
parameterType:插入语句的参数类型,可以省略
-->
<select id="queryUserById" resultType="User">
select *,user_name as userName from tb_user where id = #{id}
</select>
3.Update
Update标签:编写更新语句的statement
id:语句的唯一标识
parameterType:语句的参数类型,使用动态代理之后,需要和mapper接口中的参数类型一致,可以省略。
<!--
update:更新的statement,用来编写更新语句
id:语句的唯一标识
parameterType:语句的参数,可以省略
-->
<update id="updateUser">
UPDATE tb_user
SET user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = now()
WHERE
(id = #{id});
</update>
4.Delete
delete标签:编写删除语句的statement
id:语句的唯一标识
parameterType:语句的参数类型,使用动态代理之后,需要和mapper接口中的参数类型一致,可以省略。
<!--
delete:删除的statement,用来编写删除语句
id:语句的唯一标识
parameterType:语句的参数,可以省略
-->
<delete id="deleteUserById">
delete from tb_user where id = #{id};
</delete>
5.parameterType传入参数
CRUD标签都有一个属性parameterType,statement通过它指定接收的参数类型。
接收参数的方式有两种:
1. #{}预编译
2. ${}非预编译(直接的sql拼接,不能防止sql注入)
参数类型有三种:
- 基本数据类型
- HashMap(使用方式和pojo类似)
- Pojo自定义包装类型
5.1 ${}的用法
场景:数据库有两个一模一样的表。历史表,当前表
查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。
【示例】
第一步:添加一个接口
在UserMapper接口中,添加根据表名查询用户信息的方法:
第二步:编写一个查询的statement
在UserMapper映射文件中,添加方法对应的statement:
第三步:编写测试方法
执行方法输出(报错):只能使用${value}来传递参数,其他任何字符串都不行
如果你要动态传入的字段名是表名,并且sql执行是预编译的,这显然是不允许的,所以你必须改成非预编译的,也就是这样:
在一个参数的情况下必须,参数名称必须为value:即${value}
注意:
使用${} 去接收参数信息,在一个参数时,默认情况下必须使用${value}获取参数值,
但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名.
5.2 #{}的用法
#{}类似于sql语句中的?,但是单个参数和多个参数的使用方式稍微有点区别。
单个参数
多个参数
当mapper接口要传递多个参数时,有两种传递参数的方法:
- 默认规则获取参数{0,1,param1,param2}
- 使用@Param注解指定参数名
案例:实现一个简单的用户登录,根据username和password验证用户信息
注意:
单个参数时,#{}与参数名无关的。
多个参数时,#{}和${}与参数名(@Param)有关。
什么时候需要加@Param注解?什么时候不加?
单个参数不加,多个参数加
当你传递的是基本类型的时候建议都加 @Param
当你传递的是一个对象或是map的时候不用加 @Param
/**
* 新增用户
* @param user
*/
public void insertUser(User user);
5.3 HashMap参数
使用HashMap来传递参数
UserMapper接口方法:
/**
* 用户登陆,参数为map
* @return
*/
User loginMap(Map<String,String> map);
UserMapper配置文件:
<!--
将map的key作为参数名称来传递参数
-->
<select id="loginMap" resultType="User">
select * from tb_user where user_name = #{userName} and password = #{password}
</select>
测试用例:
@Test
public void testLoginMap(){
Map<String, String> map = new HashMap<>();
map.put("userName","zhangsan");
map.put("password","123456");
User user = userMapper.loginMap(map);
System.out.println(user);
}
#{}和${}的区别:
#{}:
- 是预编译
- 编译成占位符
- 可以防止sql注入
- 自动判断数据类型
- 一个参数时,可以使用任意参数名称进行接收
${}:
- 非预编译
- sql的直接拼接
- 不能防止sql注入
- 需要判断数据类型,如果是字符串,需要手动添加引号。
- 一个参数时,参数名称必须是value,才能接收参数。
需要判断数据类型的时候使用#{}
需要拼接的时候使用${},比如查询表名
5.4 资源文件添加前缀
select * from tb_user where user_name = '${userName}' and password = '${password}'
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/users
username=root
password=root
${}可以获取资源文件中的数据,比如mybatis-config.xml中使用$获取连接信息就是。由于映射文件中使用的${password}的参数名称正好和资源文件中的key相同,所以会获取资源文件中的数据。
解决方案:在资源文件中的key中添加前缀
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/users
jdbc.username=root
jdbc.password=root
5.5 resultMap
ResultMap是mybatis中最重要最强大的元素,使用ResultMap可以解决两大问题:
- POJO属性名和表结构字段名不一致的问题(有些情况下也不是标准的驼峰格式,比如id和userId)
- 完成高级查询,比如说,一对一、一对多、多对多。(后面讲多表再讲)
解决列名(表中的字段名称)和实体类中的属性名不一致
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName
两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名
解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 --- mybatis-config.xml 的时候
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
解决方案3:resultMap自定义映射
<!--
resultMap: 自定义结果集,自行设置结果集的封装方式
autoMapping: autoMapping属性:操作单表时,不配置默认为true,
如果pojo对象中的属性名称和表中字段名称相同,则自动映射。
单表默认为true,多表默认就会失效,需要手动配置
-->
<resultMap id="userResultMap" type="User" autoMapping="true">
<!--
配置主键映射关系
column: 表中的主键名称
property:对象中与主键对应的属性名称
<id 标签不管什么时候尽量都配置
-->
<id column="id" property="id"/>
<!--配置用户名的映射关系-->
<result column="user_name" property="userName"/>
</resultMap>
使用resultMap
<select id="queryUserById" resultMap="userResultMap">
select * from tb_user where id = #{id}
</select>
未使用resultMap
<select id="queryUserList" resultType="User">
select * from tb_user
</select>
在上面的resultMap中,主键需要通过id子标签配置,表字段和属性名不一致的普通字段需要通过result子标签配置。
那么,字段和属性名称都匹配的字段要不要配置?
这个取决于resultMap中的autoMapping属性的值:
为true时:resultMap中的没有配置的字段会自动对应。如果不配置,则默认为true。
为false时:只针对resultMap中已经配置的字段作映射。