Mybatis中有两种开发方式
- xml开发,同映射配置文件
- 注解开发
这里要讲的是注解开发
为什么会有注解开发?
因为xml开发的配置,过于繁琐,为了提升开发效率,有了注解开发,只要单表查询的都可以使用注解开发,但如果结果集的数据来自于多张表查出来的,那还是更建议使用一对一、一对多、多对多的方式,xml和注解是可以共存的。
流程
1.不需要再使用 xxxxMapper.xml 映射配置文件,取之替代的,需要在 mybatis-config.xml 核 心配置文件中,将映射器的属性改为<package name="扫描的包路径">
来让他自动扫描接口
2.编写接口和注解
3.测试
为什么不使用注解开发进行多表查询?
因为使用注解开发的时候,会先进行单表查询,然后再基于前一次单表查询的数据再继续查询另一张表,会存在多次查询的一个情况,会导致性能慢
前置准备
SQL表
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50622
Source Host : localhost:3306
Source Database : heima81
Target Server Type : MYSQL
Target Server Version : 50622
File Encoding : 65001
Date: 2019-08-20 17:08:12
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`sex` int(11) DEFAULT NULL COMMENT '0-女 1-男',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '0');
INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '1');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '0');
INSERT INTO `tb_user` VALUES ('6', '蔡徐坤', '123', '小菜', '18', '1');
对应映射类
// user
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
//0-女 1-男
private Integer sex;
}
Mybatis工具类
package com.dongmianmao.util;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class MybatisUtil {
static SqlSessionFactory ssf = null;
private MybatisUtil(){}
static{
String resource = "mybatis-config.xml";
InputStream resourceAsStream = MybatisUtil.class.getClassLoader().getResourceAsStream(resource);
ssf = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
public static SqlSession OpenSession(){
return ssf.openSession();
}
public static SqlSession OpenSession(Boolean bool){
return ssf.openSession(bool);
}
}
一、Insert 体验
需求:往 User 表中插入一条记录
在以往使用xml开发,需要编写以下映射配置文件
<insert id="addUser" >
insert into tb_user(user_name, password, name, age, sex) values('zhangsan1',123,'张三',12,1)
</insert>
当需求多了后会相对繁琐,而注解开发则是在一般的单表查询不需要使用编写xml,而是像一下这样,在对应的映射接口中使用注解
@Insert("insert into tb_user(user_name, password, name, age, sex) " +
"values(#{userName},#{password},#{name},#{age},#{sex})")
int addUser(User user);
使用 @Insert() 注解,在里面同 xml 编写SQL一致,在括号内编写 SQL语句 , 使用 #{} 来进行占位
测试类
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUserName("zhangsan11");
user.setPassword("123456");
user.setName("张三11");
user.setAge(12);
user.setSex(1);
int i = mapper.addUser(user);
if (i>0){
sqlSession.commit();
System.out.println("添加成功");
}
sqlSession.close();
}
运行以上测试代码,就可以看到控制台输出,添加成功
二、Select 体验
需求:查询所有用户
在以往使用xml开发,需要编写以下映射配置文件
<select id="findAllUser" resultType="user">
select id, user_name, password, name, age, sex from tb_user;
</select>
而使用注解开发,只需要使用 @Select() 并在里面写对应查询语句即可
@Select("select id, user_name, password, name, age, sex from tb_user")
List<User> findAllUser();
测试类
@Test
public void testFindAllUser(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user);
}
sqlSession.close();
}
就可以查出对应的记录了
三、Update 体验
需求:修改指定用户名的用户密码
注解开发,只需要使用 @Update() 并在对应接口里面写对应查询语句即可
@Update("update tb_user set password=#{password} where user_name=#{username}")
int updateUserPassword(@Param("username")String username,
@Param("password")String password);
因为传入了一个以上的参数,为了让Mybatis区分两个参数,需要使用 @Param() 来给后面的参数取名字
测试类
@Test
public void testUpdateUserPassword(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUserPassword("zhangsan11", "111111");
if (i>0){
sqlSession.commit();
System.out.println("修改密码成功");
}
sqlSession.close();
}
这样就可以修改指定的用户名密码了
四、Delete 体验
需求:根据传入id删除指定记录
注解开发,只需要使用 @Delete() 并在对应接口里面写对应查询语句即可
@Delete("delete from tb_user where id = #{id}")
int deleteUser(int id);
可以看到,在 deleteUser 这个方法中传入了一个 id 参数,用于删除指定用户
测试类
@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(15);
if (i>0){
sqlSession.commit();
System.out.println("删除用户成功");
}
sqlSession.close();
}
这样就可以删除指定 id 的行记录了
五、Results结果集映射
当查询结果列名和实体类属性名不一致时有以下解决方案
- 1.在SQL中,添加别名—as(别名和实体类中的属性名一致)
- 2.在核心配置文件中设置驼峰映射
- 3.使用resultMap标签,设置查询结果和实体类的映射关系
这里讲解决方案3,在注解中如何使用
在注解开发中,有一个全新的注解 @Resluts 实现映射,等价于 resultMap 标签
可以先看一下在 xml 开发中,配置结果集映射是怎么样的
<resultMap id="userMap" type="com.dongmianmao.Pojo.User" autoMapping="true">
<id column="user_id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
有<id>
和 <result>
标签分别表示 id 和除了 id 以外的字段
看一下==@Results== 的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Results {
String id() default "";
Result[] value() default {};
}
可以发现,前面有一个 id 属性 , 后面跟着一个 Result 数组 叫 value
在进入 Result 源码中
只有前面三个是需要使用的,而后面的,都是关于多表查询的,由于前面说了性能慢,这里只提单表查询需要使用的
而在注解开发中区分主键,是不像 xml 开发中一样有个专门的标签的,在注解开发中,所有字段都是围绕着末尾这两个属性展开,而区分id的方法,则是在数组元素中的 @Result 中,添加一个 id=true 属性
需求:将查询全部User方法中的SQL语句中的字段与实体类的映射字段一一对应
先针对先前的查询语句进行一个结果集映射
@Select("select id, user_name, password, name, age, sex from tb_user")
@Results(id = "resultMap" ,
value = {
@Result(column = "id" , property = "id" , id=true),
@Result(column = "user_name",property = "userName")
})
List<User> findAllUser();
只对不一致的字段和id进行指定,其他的会自动添加
测试类同上,一开始的测试类如果没开驼峰映射,可能会导致userName是Null的情况
@Test
public void testFindAllUser(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user);
}
sqlSession.close();
}
六、模糊查询
需求:查询男性用户,如果输入了用户名,按用户模糊查询,如果没有输入用户名,那就查询所有男性用户
根据需求写出的SQL
select id, user_name, password, name, age,sex
from tb_user
where sex = 1 and name like '%张%';
然后在接口中定义一个方法,来查询用户
/**
* 根据传入的用户名来进行查询用户
* @param name 用户名
* @return 用户列表
*/
@Select("select id, user_name, password, name, age,sex\n" +
"from tb_user where sex = 1 and name like '%#{name}%';")
List<User> findUserByLike(String name);
可以看到,最后的 ‘%#{name}%’ 这个占位符是在单引号里的,是不生效的
有两种做法
1.使用传参的方式把整个格式给包括了
/**
* 根据传入的用户名来进行查询用户
* @param name 用户名
* @return 用户列表
*/
@Select("select id, user_name, password, name, age,sex\n" +
"from tb_user where sex = 1 and name like '#{name};")
List<User> findUserByLike(String name);
修改为这样,然后测试类
@Test
public void testFindUserByLike(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findUserByLike("%张%");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
直接把 % 传入进去,也能符合需求,但实际开发中,不会这样干,更多的还是使用第二种方式
2.使用==concat()==来拼接SQL语句
这里先修改接口方法中的SQL语句
/**
* 根据传入的用户名来进行查询用户
* @param name 用户名
* @return 用户列表
*/
@Select("select id, user_name, password, name, age,sex\n" +
"from tb_user where sex = 1 and name like concat('%',#{name},'%');")
List<User> findUserByLike(String name);
相应的,测试类修改为
@Test
public void testFindUserByLike(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findUserByLike("张");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
最后也符合需求
七、动态SQL
有两种方式可以实现
1.使用 @xxxProvider() 注解(xxx:对应的增删改查操作)
需求:查询男性用户,如果输入了用户名,按用户模糊查询,如果没有输入用户名,那就查询所有男性用户
这里针对 六、模糊查询的需求进行优化,因为那的写法,无法实现后面部分的需求,如果没有输入用户名,那就查询所有男性用户
因为这里是关于 Select 的操作,所以使用,@SelectProvider(),使用这个注解需要先创建一个类,在一个类中来书写关于SQL的判断规则,这里我们创建一个叫做 SqlProvider 的类,
package com.dongmianmao.Dao;
import org.apache.ibatis.annotations.Param;
import org.junit.Test;
public class SqlProvider {
public String findUserByLike(@Param("name")String name){
String sql = "select id, user_name as userName, password, name, age,sex from tb_user where sex = 1";
if (name!=null && !name.equals("")){
sql+=" and name like concat('%',#{name},'%')";
}
return sql;
}
}
可以看到,在这里面写了一个方法 findUserByLike 来对SQL进行判断,默认的SQL是查询所有男性记录
如果传入的的 name 不为空也不为 null 则在最开始查出所有男性记录的SQL的基础上,拼接模糊查询,最后返回SQL
创建好类后,接下来修改接口内的方法
/**
* 根据传入的用户名来进行查询用户
* @param name
* @return
*/
// @Select("select id, user_name as userName, password, name, age,sex" +
// "from tb_user where sex = 1 and name like concat('%',#{name},'%');")
@SelectProvider(type = SqlProvider.class,method = "findUserByLike")
List<User> findUserByLike(@Param("name") String name);
把最开始使用的 @Selecty 给注释了,不再使用这个,而是使用 @SelectProvider ,这里面有两个参数
参数名 | 参数值 | 参数作用 | 是否必须 |
---|---|---|---|
type | 写有动态sql规则的类的字节码文件 | 找到书写动态sql规则的类 | 是 |
method | 在书写动态sql规则的类中的规则方法 | 通过字节码文件拿到成员方法再去执行 | 是 |
在字节码文件中去执行成员方法,还需要去指定对应的入参,而这个入参则是在 接口 和 类中的入参 使用==@Parma== 去指定一样的名字 | |||
这里的 规则方法 和 接口 中均指定了入参为 @Parma(“name”) ,==必须得一致 == |
测试类
@Test
public void testFindUserByLike(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findUserByLike("张");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
运行后就实现了对应的需求
2.使用 @xxxProvider() 注解 + SQL类
本质上和第一种方法是一样的,只不过不一样的是,第一种方法使用的是字符串直接拼接,而第二种放啊使用的是Mybatis中提供的一个对象 SQL 中的 API方法 来进行 SQL 语句的拼接
即原来的 SqlProvider 类中的方法改为
package com.dongmianmao.Dao;
import com.sun.org.apache.regexp.internal.RE;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;
import org.junit.Test;
public class SqlProvider {
public String findUserByLike(@Param("name")String name){
SQL sql = new SQL();
sql=sql.SELECT("*").FROM("tb_user").WHERE("sex = 1");
if (name!=null && !name.equals("")){
sql.AND().WHERE("name like concat('%',#{name},'%')");
}
return sql.toString();
}
}
API 的取名如实际效果,替代了原有的关键字
接口方法一致不做任何改变
/**
* 根据传入的用户名来进行查询用户
* @param name
* @return
*/
// @Select("select id, user_name as userName, password, name, age,sex" +
// "from tb_user where sex = 1 and name like concat('%',#{name},'%');")
@SelectProvider(type = SqlProvider.class,method = "findUserByLike")
List<User> findUserByLike(@Param("name") String name);
测试类
@Test
public void testFindUserByLike(){
SqlSession sqlSession = MybatisUtil.OpenSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findUserByLike("张");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
运行后可以看到,符合预期
总结:相对第一种方法来讲,第二种方法会更麻烦一点,没有第一种方法来的快,还是看个人习惯