一、XML映射器/SQL映射文件
- 基本介绍
1、MyBatis的真正强大在于它的语句映射(在XxxMapper.xml配置),由于它的异常强大,如果拿它跟具有相同功能的JDBC代码进行对比,你会立即发现省掉了将近95%的代码。MyBatis致力于减少使用成本,让用户能更专注于SQL代码。
<select id="selectMonsterById" resultType="Monster">
select * from `monster` where id=#{id}
</select>
</mappers>
2、SQL映射文件常用的几个顶级元素(按照应被定义的顺序列出):
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- parameterType – 将会传入这条语句的参数的类全限定名或别名
parameterMap– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
- 匹配XxxMapper.xml的4种方式
但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。MyBatis通过以下4种方式查找实现了接口的XXMapper.xml文件。如果没有写,或者写错了路径、文件名,都会报错Type interface *** is not known to the MapperRegistry。
- 1.使用映射器接口实现类的完全限定类名。可以用来配置用@注解实现的映射器。
> \<mapper class="com.stein.mapper.MonsterAnnotation"/>。
- 2.使用相对于类路径的资源引用
> \<mapper resource="com/stein/mapper/monsterMapper.xml"/>
- 3.将包内的映射器接口全部注册为映射器。直接写包名,在包内搜索。
> \<package name="com.stein.mapper"/>
- 4.使用完全限定资源定位符(URL)(不常用)
二、新建演示Module
新建一个干净的环境进行演示,同时再熟悉创建过程。
1.新建Module(不是project)
选择Maven,注意父项是不是上图中顶层的mybatis
2.检查子模组的pom.xml文件,是不是包含了父模组;
检查父模组的pom.xml文件,是不是包含了子模组。
3.复制mybatis_quickstart的框架文件到当前模组中
清空MonsterMapper接口和xml的实现内容
清空mybatis-config.xml中的<mappers>里面的内容,仅保留MonsterMapper的映射路径
Test只保留初始化部分
测试,无报错,正常显示:monsterMapper的运行类型是:class com.sun.proxy.$Proxy7
三、基本使用
1.insert、delete、update、select这个我们在前面讲解过,分别对应增删改查的方法和SQL语句的映射。
2.如何获取到刚刚添加的Monster对象的id主键【useGeneratedKeys="true"】
<insert id="insertMonster" parameterType="com.stein.entity.Monster" useGeneratedKeys="true" keyProperty="id">
INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`)--
values(#{age},#{birthday},#{email},#{gender},#{name},#{salary})
</insert>
四、parameterType(输入参数类型)
parameterType(输入参数类型)
1.传入简单类型,比如按照id查Monster(前讲过)
比如:
Monster selectMonsterById(Integer monsterId);
2.传入POJO类型,查询时需要有多个筛选条件
3.当有多个条件时,传入的参数就是Pojo类型的Java对象,比如这里的Monster对象
4.当传入的参数类是String时,也可以使用${}方式来收参数
案例1:请查询id=1或者name='白骨精'的妖怪
添加接口方法:
public interface MonsterMapper {
//通过id或者名字查询
public List<Monster> findMonsterByNameORId(Monster monster);
}
实现/配置方法MonsterMapper.xml
<mapper namespace="com.stein.mapper.MonsterMapper">
<!--`id`表示数据库中的字段名,#{id}表示monster的属性monster.id-->
<select id="findMonsterByNameORId" parameterType="Monster" resultType="Monster">
select * from `monster` where `id`=#{id} or `name`=#{name}
</select>
</mapper>
测试
@Test
public void findMonsterByNameORId(){
Monster monster = new Monster();
monster.setId(9);
monster.setName("狐狸精");
List<Monster> monsters=monsterMapper.findMonsterByNameORId(monster);
for (Monster m : monsters) {
System.out.println("monster="+m);
}
if(sqlSession != null){
sqlSession.close();
System.out.println("查询成功");
}
}
案例2:请查询name中包含"精"的妖怪
这儿“包含”用的是模糊查询
1.先在DB里面测试sql语句是否正确
-- %百分号是SQL中的通配符,代表零个、一个或多个字符。
select * from `monster` where `name` like "%精%"
2.添加接口方法
public interface MonsterMapper {
//查询名字中含义'精'妖怪
public List<Monster> findMonsterByName(String name);
}
3.使用映射器实现接口方法
select * from `monster` where `name` like "%${name}%"
注意区别前面的【%精%】 是写死的,要换成参数,等待传入。这儿有了两种写法:
1."% #{name} %" ,可以看见是#{}的写法,后面运行代码看日志里面的语句是:
select * from `monster` where `name` like "%?%"
这是一个预编译指令,先用?来替代,然后再把变量替换?问号,结果出错了:Could not set parameters for mapping。
2."% ${name} %" ,可以看见是${}的写法,后面运行代码看日志里面的语句是:
select * from `monster` where `name` like "%精%"
可以发现直接使用变量的值进行替换了,可以正常运行。
这儿同时也给出了#{},#{}的区别。前者是预编译指令,可以防止注入;后者是直接替换,不能防止注入。
顺带补充说明,这儿单引号'% ${name} %',或者双引号"% ${name} %"都能使用。但本人推崇使用双引号。因为对应Linux中的单引号‘${var}’不能被替换字符。
<mapper namespace="com.stein.mapper.MonsterMapper">
<select id="findMonsterByName" parameterType="String" resultType="Monster">
select * from `monster` where `name` like "%精%"
</select>
</mapper>
4.测试
@Test
public void findMonsterByName(){
String key="精";
List<Monster> monsters=monsterMapper.findMonsterByName(key);
for (Monster m : monsters) {
System.out.println("monster="+m);
}
if(sqlSession != null){
sqlSession.close();
}
System.out.println("查询完毕");
}
五、传入类型:HashMap(重点)
1.HashMap传入参数更加灵活,比如可以灵活的增加查询的属性,而不受限于Monster这个Pojo属性本身
2.演示如何遍历一个List<Map<String,Object>>的数据类型
应用实例1·传入HashMap
要求:声明一个方法,按传入参数是HashMap的方式,查询id>10并且salary大于40的所有妖怪
不在使用POJO传递参数,POJO有属性的限制,使用更自由的HashMap
1.测试SQL语句
select * from `monster` where `id`>10 and `salary` > 10;
2.添加接口方法
List<Monster> findMonsterByIdAndSalaryUseParameterHashMap(Map<String,Object> map);
3.实现方法
<select id="findMonsterByIdAndSalaryUseParameterHashMap" parameterType="map" resultType="Monster">
select * from `monster` where `id`>#{id} and `salary` > #{salary};
</select>
4.测试
@Test
public void findMonsterByIdAndSalaryUseParameterHashMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("id",10);
map.put("salary",40);
List<Monster> monsters=monsterMapper.findMonsterByIdAndSalaryUseParameterHashMap(map);
for (Monster m : monsters) {
System.out.println("monster="+m);
}
if(sqlSession != null){
sqlSession.close();
}
System.out.println("查询成功");
}
应用实例2-传入和返回都是HashMap
要求:将上面的方法的改成返回参数也以HashMap的类型
1.测试的SQL语句不变
2.添加接口方法
//传入和返回值都是HashMap的方式
List<Map<String,Object>> findMonsterByIdAndSalary_ParameterHashMap_ResultHashMap(Map<String,Object> map);
3.实现方法
注意这里的parameterType和resultType
<select id="findMonsterByIdAndSalary_ParameterHashMap_ResultHashMap" parameterType="map" resultType="map">
select * from `monster` where `id`>#{id} and `salary` > #{salary};
</select>
4.测试
@Test
public void findMonsterByIdAndSalary_ParameterAndResultHashMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("id",10);
map.put("salary",40);
List<Map<String,Object>> monsterList=monsterMapper.findMonsterByIdAndSalary_ParameterHashMap_ResultHashMap(map);
for (Map<String, Object> monsterMap : monsterList) {
//System.out.println("monster="+monsterMap);
Set<Map.Entry<String, Object>> entries = monsterMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
System.out.println(entry.getKey()+" : "+entry.getValue());
}
System.out.println("==========分割线=============");
}
if(sqlSession != null){
sqlSession.close();
}
System.out.println("查询成功");
}
六、传入类型:resultMap(结果集映射)
基本介绍
当实体类的属性和表的字段名字不一致时,我们可以通过resultMap进行映射,从而屏蔽实体类属性名和表的字段名的不同。
演示案例1-参数的映射
本质上是通过SQL的语句,配合POJO属性的替换#{}来完成映射的。
1.DB里面创建表user
create table USER(
user_id INT not null auto_increment,
user_email varchar(255) default "",
user_name varchar(255) default "",
PRIMARY KEY (user_id)
)CHARSET=utf8
2.创建java的POJO/Entity
故意设计的属性名字和DB不一致
完善getter,setter,NoargsConstructor,ToString等
public class User {
//一样的,因为自增长,也看不到这个属性
private int user_id;
//_换成了大写
private String userEmail;
//_换成了小写
private String username;
}
3.创建UserMapper接口和方法
public interface UserMapper {
//插入user方法
void insertUser(User user);
}
4.测试SQL语句
insert into `user`(`user_email`,`user_name`) values("94595892@qq.com","stein")
5.创建UserMapper.xml,使用映射器实现该方法
<mapper namespace="com.stein.mapper.UserMapper">
<insert id="insertUser" parameterType="User">
insert into `user`(`user_email`,`user_name`) values(#{userEmail},#{username})
</insert>
注意:
①user()表中的字段对应DB,values()的字段对应Java的Entity,从而完成不同字段的映射。
②需要将UserMapper.xml这个文件在mybatis-config.xml进行注册。可以单文件注册,也可以通过文件夹进行注册。
<mappers>
<!--原生xml方式引入(注册)MonsterMapper.xml文件-->
<!--<mapper resource="com/stein/mapper/MonsterMapper.xml"/>-->
<!--<mapper resource="com/stein/mapper/UserMapper.xml"/>-->
<!--通过添加文件夹的方式注册xml文件-->
<package name="com.stein.mapper"/>
</mappers>
③ParameterType写的是简称,所以要在Alias里面进行配置,否则要用全名
<typeAliases>
<!--<typeAlias type="com.stein.entity.Monster" alias="Monster" />-->
<!--
如果一个包下有很多的类,我们可以直接引入包,这样
该包下面的所有类名,可以直接使用
-->
<package name="com.stein.entity"/>
</typeAliases>
6.创建测试文件,进行测试
public class UserTest {
private SqlSession sqlSession;
private UserMapper userMapper;
@Before
public void init(){
sqlSession= MybatisUtils.getSqlSession();
//获取到MonsterMapper对象?实际是代理对象 class com.sun.proxy.$Proxy9
//底层是使用了动态代理机制
userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println("userMapper的运行类型是:"+userMapper.getClass());
}
@Test
public void insertUser(){
User user = new User();
user.setUsername("PapaPig");
user.setUserEmail("papapig@gmail.com");
userMapper.insertUser(user);
if(sqlSession!=null){
sqlSession.commit();
sqlSession.close();
}
System.out.println("添加用户成功");
}
}
演示案例2-返回结果的映射
要求:返回所以的user查询结果
1.添加接口方法UserMapper
List<User> findAllUser();
2.使用映射器实现该方法
常规方法:
<select id="findAllUser" resultType="User">
select * from `user`
</select>
通过后期的测试可以发现,结果是查到了,但是封装到User的时候,因为属性名不匹配,导致属性值最终为null。
解决方法:使用resultMap来对不一致的地方进行映射
<resultMap id="userResultMapper" type="User">
<result property="userEmail" column="user_email"/>
<result property="username" column="user_name"/>
</resultMap>
<select id="findAllUser" resultMap="userResultMapper">
select * from `user`
</select>
<select id="findAllUser" resultType="User" resultMap="userResultMapper">
原来的resultType就替换成了resultMap,=等号指向它的id名字
type是结果要返回的最终类型
property是POJO/Entity的属性,column是DB表的字段名。从这里,也可以看出property和column的区别了。
3.测试
@Test
public void findUser(){
List<User> allUser = userMapper.findAllUser();
for(User user:allUser){
System.out.println(user);
}
if(sqlSession!=null){
sqlSession.close();
}
System.out.println("查询完毕");
}
注意事项和细节
1、解决表字段和对象属性名不一致,也支持使用字段别名。
<select id="findAllUser" resultType="User">
select user_id,user_email as userEmail,user_name as username from `user`
</select>
2、如果是MyBatis-PIus处理就比较简单(后期会用到),可以使用注解@TableField来解决实体字段名和表字段名不一致的问题,还可以使用@TableName来解决实体类名和表名不一致的问题