Mybatis教程 | 第八篇:Mybatis的注解配置

Mybatis为用户提供了快速的开发方式,因为有时候大量的XML配置文件的编写时非常繁琐的,因此Mybatis也提供了更加简便的基于注解(Annnotation)的配置方式。

Mybatis的注解位于org.apache.ibatis.annotations包下,常用的注解如下:

注解 说明
@Select 映射查询的SQL语句
@Insert 映射插入的SQL语句
@Update 映射更新的SQL语句
@Delete 映射删除的SQL语句
@Results 多个结果集(Result)映射,有id和value两个属性,id指定当前结果集的唯一标识,value属性指定@Result映射,如果当前@Results结果集不被其它映射引用,可以不指定id,value可以默认不写
@Result 在列和属性之间的单独结果映射。属性包括:id、column、property、javaType、jdbcType、type Handler、one、many。id属性是一个布尔值,表示是否用于主键映射。one 属性是单独的联系,和xml配置中的相似,而many属性是对集合而言的,和XML配置的相似。
@ResultMap 指定一个结果集映射,可引入其它的@Results映射,类似于XML中select标签中的resultMap属性。@ResultMap和@Results不能单独同时存在,也就是说,如果当前dao层接口中只有单个查询方法,只能使用@Results。只有当出现两个及以上的查询方法时,才能出现@ResultMap去引用@Results映射(这一点就赶不上XML的灵活性了)。
@Options 提供配置选项的附加值,它们通常在映射语句上作为附件功能配置出现。也就是一般和CRUD的注解一起出现,比如在XML中标签有useGeneratedKeys和keyProperty属性,但是@Insert注解没有,需要利用@Options作为附件功能配置一起使用,还有类似于
@One 复杂类型的单独属性映射值,必须指定select属性,表示已经映射的SQL语句的完全限定名
@Many 复杂类型的集合属性映射,必须指定select属性,表示已映射的SQL语句的完全限定名
@Param 当映射器方法需要多个参数时,这个注解可以被应用于映射器方法参数来给每个参数取一个别名。否则,多参数将会以它们的顺序位置和SQL语句中表达式进行映射,这是默认的。使用@Param("id"),SQL中的参数应该被命名为#{id}。在前面的XML中已经使用过该注解了
@SelectProvider select语句的动态SQL映射。允许指定一个类名和一个方法在执行时返回允许的查询语句。有两个属性,type和method,type指定类的完全限定名,method是该类中的那个方法
@InsertProvider Insert的动态SQL映射,属性和@SelectProvider相同
@UpdateProvider Update语句的动态SQL映射。属性和@SelectProvider相同
@DeleteProvider Delete语句的动态SQL映射。属性和@SelectProvider相同

 

insert、select、update和delete测试

首先创建Mybatis的使用环境(参考传送门:快速开始Mybatis),并创建如下两张表和对应的持久化对象。

注意:配置文件记得加上dao层接口的扫描


  	
    
  

Mybatis教程 | 第八篇:Mybatis的注解配置_第1张图片

持久层对象

public class Dept implements Serializable {
	
	private static final long serialVersionUID = 1L;

	private Integer deptId;
	
	private String deptName;
	
	private List emps;//一个部门有多个员工
	
	//省略get/set和构造方法
}
public class Emp implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private Integer empId;
	
	private String empName;
	
	private String empAddr;
	
	private Dept dept;//一个员工只属于一个部门
	
	//省略set/get和构造方法
}

dao层接口定义CRUD的方法

public interface DeptDao {

	/**根据id查找dept**/
	@Select("SELECT * FROM tb_dept WHERE dept_id = #{deptId}")
	@Results({
			@Result(id=true,column="dept_id",property="deptId"),
			@Result(column="dept_name",property="deptName")
		})
	@Select("SELECT * FROM tb_dept WHERE dept_id = #{deptId}")
	Dept getDeptById(Integer deptId);
	
	/**新增dept**/
	@Insert("insert into tb_dept (dept_name) value (#{deptName})")
	int saveDept(Dept dept);

	/**修改dept**/
	@Update("update tb_dept set dept_name = #{deptName} where dept_id = #{deptId}")
	int updateDept(Dept dept);
	
	/**根据id删除dept,一定不要漏掉条件,不然只好跑路了哦**/
	@Delete("delete from tb_dept where dept_id = #{dept_id}")
	int deleteDeptById(Integer deptId);
}

1、执行select测试代码和结果

public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
		//根据id查找
		Dept dept = deptDao.getDeptById(1);
		System.out.println(dept.toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_dept WHERE dept_id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Dept [deptId=1, deptName=研发中心, emps=null]

单独测试下@ResultMap,再次提醒,@ResultMap和@Results不能单独同时存在,下面的示例声明了两个查询语句,具体原因已经在上表的@ResultMap栏说明。

/**根据id查找dept**/
	@Results(id="deptMap",value={
		@Result(id=true,column="dept_id",property="deptId"),
		@Result(column="dept_name",property="deptName")
	})
	
	@Select("SELECT * FROM tb_dept WHERE dept_id = #{deptId}")
	@ResultMap("deptMap")
	Dept getDeptById(Integer deptId);
	
	@Select("select * from tb_dept")
	@ResultMap("deptMap")//注意这里
	List listDept();

执行

List depts = deptDao.listDept();
DEBUG [main] - ==>  Preparing: select * from tb_dept 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 2
[Dept [deptId=1, deptName=研发中心, emps=null], Dept [deptId=2, deptName=市场中心, emps=null]]

2、执行insert测试代码和结果

//新增
Dept dept = new Dept();
dept.setDeptName("取经四人组");
int result = deptDao.saveDept(dept);
System.out.println("受影响的行 :" + result);
sqlSession.commit();
sqlSession.close();
DEBUG [main] - ==>  Preparing: INSERT INTO tb_dept (dept_name) VALUES (?) 
DEBUG [main] - ==> Parameters: 取经四人组(String)
DEBUG [main] - <==    Updates: 1
受影响的行 :1

那么。我们配合@Options就能获取刚插入数据的主键了

/**新增dept**/
@Insert("INSERT INTO tb_dept (dept_name) VALUES (#{deptName})")
@Options(useGeneratedKeys=true,keyProperty="deptId")
int saveDept(Dept dept);
//新增
Dept dept = new Dept();
dept.setDeptName("取经四人组");
deptDao.saveDept(dept);
System.out.println("返回主键" + dept.getDeptId());
sqlSession.commit();
sqlSession.close();
DEBUG [main] - ==>  Preparing: INSERT INTO tb_dept (dept_name) VALUES (?) 
DEBUG [main] - ==> Parameters: 取经四人组(String)
DEBUG [main] - <==    Updates: 1
返回主键2013

3、执行update修改操作

/**修改dept**/
@Update("update tb_dept set dept_name = #{deptName} where dept_id = #{deptId}")
int updateDept(Dept dept);
//修改
Dept dept = new Dept();
dept.setDeptName("取经四人组+白龙马");
dept.setDeptId(3);
int result = deptDao.updateDept(dept);
System.out.println("受影响的行 " + result);
sqlSession.commit();
sqlSession.close();
DEBUG [main] - ==>  Preparing: update tb_dept set dept_name = ? where dept_id = ? 
DEBUG [main] - ==> Parameters: 取经四人组+白龙马(String), 3(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行 1

4、delete删除测试

/**根据id删除dept,一定不要漏掉条件,不然只好跑路了哦**/
@Delete("delete from tb_dept where dept_id = #{dept_id}")
int deleteDeptById(Integer deptId);
//删除
int result = deptDao.deleteDeptById(3);
System.out.println("受影响的行 :" + result);
sqlSession.commit();
sqlSession.close();
DEBUG [main] - ==>  Preparing: delete from tb_dept where dept_id = ? 
DEBUG [main] - ==> Parameters: 3(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行 :1

关联映射查询测试

关联映射查询详解传送门:Mybatis关联映射查询

1、通过dept查询当前部门下的所有员工(一对多)

@Select("SELECT * FROM tb_dept WHERE dept_id = #{deptId}")
@Results(id="deptMap",value={
			@Result(id=true,column="dept_id",property="deptId"),
			@Result(column="dept_name",property="deptName"),
			@Result(column="dept_id",property="emps",	
 many=@Many(select="com.zepal.mybatis.dao.EmpDao.listEmpByDeptId",
fetchType=FetchType.LAZY))
		})
Dept getDeptById(Integer deptId);
public interface EmpDao {
	
	@Select("select * from tb_emp where dept_id = #{deptId}")
	@Results({
		@Result(id=true,column="emp_id",property="empId"),
		@Result(column="emp_name",property="empName"),
		@Result(column="emp_addr",property="empAddr")
	})
	List listEmpByDeptId(Integer deptId);
}
public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
		Dept dept = deptDao.getDeptById(1);
		System.out.println(dept.getEmps().toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_dept WHERE dept_id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select * from tb_emp where dept_id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 4
[Emp [empId=1, empName=孙悟空, empAddr=花果山, dept=null], Emp [empId=2, empName=猪八戒, empAddr=高老庄, dept=null], Emp [empId=3, empName=沙悟净, empAddr=流沙河, dept=null], Emp [empId=4, empName=唐三藏, empAddr=长安, dept=null]]

2、通过emp查询员工及其归属部门dept(一对一)

@Select("select * from tb_emp where emp_id = #{empId}")
	@Results({
		@Result(id=true,column="emp_id",property="empId"),
		@Result(column="emp_name",property="empName"),
		@Result(column="emp_addr",property="empAddr"),
		@Result(column="dept_id",property="dept",
		one=@One(select="com.zepal.mybatis.dao.DeptDao.getDeptById",fetchType=FetchType.LAZY))
	})
	Emp getEmpById(Integer empId);
@Select("SELECT * FROM tb_dept WHERE dept_id = #{deptId}")
	@Results({
			@Result(id=true,column="dept_id",property="deptId"),
			@Result(column="dept_name",property="deptName")
		})
	Dept getDeptById(Integer deptId);
public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		EmpDao empDao = sqlSession.getMapper(EmpDao.class);
		Emp emp = empDao.getEmpById(1);
		System.out.println(emp.toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: select * from tb_emp where emp_id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_dept WHERE dept_id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Emp [empId=1, empName=孙悟空, empAddr=花果山, dept=Dept [deptId=1, deptName=研发中心, emps=null]]

剩下的还有一种关系就是多对多的关系,其实抽离出来还是通过一个对象去查询多个对象,那么就应该用many=@Many。参考这篇Mybatis关联映射详解实现即可。再次重提,核心就是要抓住源对象和目标对象,其次就是,不能在通过A对象去关联查询B对象的同时,又在通过B对象去关联查询A对象,这里一个无限递归的逻辑。

动态SQL测试

在使用注解的情况下,Mybatis利用@SelectProvider、@UpdateProvider、@DeleteProvider、@InsertProvider,来帮助构建动态SQL语句。

它们拥有相同的属性type和method。type指向生成构建动态SQL的类,method指向当前类下相关联的具体方法。

SQL类的常用方法如下表:

方法(多个参数的重载时3.4.2版本开始的) 说明
T SELECT(String columns)、T SELECT(String... columns) 开始或追加select子句,有两种重载方式,即参数允许是一个或多个String类型的,编译出来就sql中的select id,name,....语句
T FROM(String table)、T FROM(String... tables) 启动或追加FROM子句,参数通常是表名,也有两种重载方式,允许一个或多个参数
T JOIN(String join)、T JOIN(String... joins) 向JOIN子句添加一个新的查询条件,该参数通常也是一个表名,也可以包括一个标准的查询返回结果,理解同sql语句的join关键字
T INNER_JOIN(String join)、T INNER_JOIN(String... joins) 同JOIN子句,内连接方式
T LEFT_OUTER_JOIN(String join)、T LEFT_OUTER_JOIN(String... joins) 同JOIN子句,左外连接方式
T RIGHT_OUTER_JOIN(String join)、T RIGHT_OUTER_JOIN(String... joins) 同JOIN子句,右外连接方式
T WHERE(String conditions)、T WHERE(String... conditions) 追加一个新的WHERE条件子句,可以多次调用
T OR()、T AND() 拆分当前WHERE条件子句,sql语句中的与和或的逻辑
T GROUP_BY(String columns)、T GROUP_BY(String... columns) 追加一个新的GROUP BY子句,理解同sql语句总的GROUP BY关键字,同样是两种重载方式,支持一个或多个参数
T HAVING(String conditions)、T HAVING(String... conditions) 追加一个新的HAVING子句条件,理解同sql关键字HAVING,同样是两种重载方式,支持一个或多个参数
T ORDER_BY(String columns)、T ORDER_BY(String... columns) 追加一个新的ORDER BY排序子句条件,理解同sql关键字ORDER BY,同样是两种重载方式,支持一个或多个参数
T INSERT_INTO(String tableName) 构建一个insert into子句,理解同sql关键字insert into....,参数是表名
T VALUES(String columns, String values) 只能配合INSERT_INTO使用,第一个参数是要插入的目标字段,第二个参数是要插入的值
T DELETE_FROM(String table) 构建DELETE FROM子句。参数只能是表名,理解同sql关键字delete from....
T UPDATE(String table) 构建UPDATE子句,理解同sql关键字update,参数只能是表名
T SET(String sets)、T SET(String... sets) 追加一个更新语句SET列表,只能配合UPDATE使用,有两种重载方式。

 

注意:如果要使用占位符#{},那么基于注解的动态SQL方法只允许接收java对象或Map类型的参数,可以无参数条件。也就是说,dao层接口某个方法如果指定了需要动态构建SQL,那么dao层接口也只能接收java对象、Map类型作为参数,可以是无参数。看一下示例

WHERE("dept_id = #{deptId}");

那么参数类型就只能是java对象或Map类型。这里java对象特指POJO对象,不能是String、Integer等等之类的。

如果不使用占位符#{},看下面示例

WHERE("dept_id = " + deptId);

那么,对参数类型就没有特定要求了,但是这里问题又来了,这样强行加参数的话,就不能使用Mybatis的类型解析器了,可以试一下将String传递进去,会因为没有单引号('',因为sql中的字符串是单引号)而引起错误,所以要注意这个问题。

1、SELECT查询

/**动态查询**/
@SelectProvider(type=EmpProvider.class,method="getEmpByDeptIdWithProvider")
	@Results({
		@Result(id=true,column="emp_id",property="empId"),
		@Result(column="emp_name",property="empName"),
		@Result(column="emp_addr",property="empAddr")
	})
List listEmpByDeptId(Integer deptId);
public class EmpProvider {

	public String getEmpByDeptIdWithProvider(Integer deptId) {
		return new SQL() {
			{
				SELECT("*");
				FROM("tb_emp");
                //在这里加了if作为条件判断
				if(deptId != null && deptId!=0) {
					WHERE("dept_id = " + deptId);
				}else {
					WHERE("dept_id = 1");
				}
			}
		}.toString();
	}
}
public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		EmpDao empDao = sqlSession.getMapper(EmpDao.class);
		List emps = empDao.listEmpByDeptId(0);
		System.out.println(emps.toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_emp WHERE (dept_id = 1) 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 4
[Emp [empId=1, empName=孙悟空, empAddr=花果山, dept=null], Emp [empId=2, empName=猪八戒, empAddr=高老庄, dept=null], Emp [empId=3, empName=沙悟净, empAddr=流沙河, dept=null], Emp [empId=4, empName=唐三藏, empAddr=长安, dept=null]]

2、动态新增并取回主键

@InsertProvider(type=EmpProvider.class,method="saveEmpWithProvider")
@Options(useGeneratedKeys=true,keyProperty="empId")
int saveEmp(Emp emp);
public String saveEmpWithProvider(Emp emp) {
		return new SQL() {
			{
				INSERT_INTO("tb_emp");
				VALUES("emp_name", "#{empName}");
				VALUES("emp_addr", "#{empAddr}");
				VALUES("dept_id", "#{dept.deptId}");
			}
		}.toString();
	}
public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		EmpDao empDao = sqlSession.getMapper(EmpDao.class);
		Emp emp = new Emp();
		emp.setEmpName("金角大王");
		emp.setEmpAddr("莲花洞");
		emp.setDept(new Dept(1, "", null));
		int result = empDao.saveEmp(emp);
		System.out.println("受影响的行 -- " + result);
		System.out.println("主键 --- " + emp.getEmpId());
		sqlSession.commit();
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: INSERT INTO tb_emp (emp_name, emp_addr, dept_id) VALUES (?, ?, ?) 
DEBUG [main] - ==> Parameters: 金角大王(String), 莲花洞(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行 -- 1
主键 --- 10

3、动态修改

@UpdateProvider(type=EmpProvider.class,method="updateEmpWithProvider")
	int updateEmp(Map map);
public String updateEmpWithProvider(Map map) {
		return new SQL() {
			{
				UPDATE("tb_emp");
				SET("emp_name=#{empName},emp_addr=#{empAddr}");
				WHERE("emp_id = #{empId}");
			}
		}.toString();
	}
public class Test{

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession();
		EmpDao empDao = sqlSession.getMapper(EmpDao.class);
		Map map = new HashMap<>();
		map.put("empName", "银角大王");
		map.put("empAddr", "平顶山莲花洞");
		map.put("empId", 10);
		int result = empDao.updateEmp(map);
		System.out.println("受影响的行 --- " + result);
		sqlSession.commit();
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: UPDATE tb_emp SET emp_name=?,emp_addr=? WHERE (emp_id = ?) 
DEBUG [main] - ==> Parameters: 银角大王(String), 平顶山莲花洞(String), 10(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行 --- 1

4、动态删除

@DeleteProvider(type=EmpProvider.class,method="deleteEmpByEmpNameAndDeptIdWithProvider")
int deleteEmpByEmpNameAndDeptId(Map map);
public String deleteEmpByEmpNameAndDeptIdWithProvider(Map map) {
		return new SQL() {
			{
				DELETE_FROM("tb_emp");
				WHERE("emp_name = #{empName}");
				AND();
				WHERE("dept_id = #{deptId}");
			}
		}.toString();
	}
Map map = new HashMap<>();
map.put("empName", "银角大王");
map.put("deptId", 1);
int result = empDao.deleteEmpByEmpNameAndDeptId(map);
System.out.println("受影响的行--"+result);
sqlSession.commit();
sqlSession.close();
DEBUG [main] - ==>  Preparing: DELETE FROM tb_emp WHERE (emp_name = ?) AND (dept_id = ?) 
DEBUG [main] - ==> Parameters: 银角大王(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
受影响的行--1

到此,基于注解的动态SQL已经演示完毕,已经尽可能的把各种情况,和各种判断条件已经涵盖了,动态SQL是一个很灵活的东西,所以需要变着花样的去多操作,它能实现你所有的骚操作和骚想法。

总结

注解形式和XML形式都是Mybatis实现持久化的方式,XML方式能实现的,注解都能实现,但是相比较而言,XML形式可读性更强,比如,一打开mapper文件一切都一目了然,特别是在动态SQL的表现形式上,当SQL语句复杂之后,注解的形式不好写,而且不好读,但是注解的动态SQL也有一定好处,就是符合面向对象的操作,而且在条件判断上,更容易被掌控,因为XML的动态SQL形式是基于OGNL的,像大于符号这些都有一些特定的写法。两种方式都应该掌握,具体应用那种方式,还得根据团队协商。


此篇完结

你可能感兴趣的:(mybatis)