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配置中的 |
@ResultMap | 指定一个结果集映射,可引入其它的@Results映射,类似于XML中select标签中的resultMap属性。@ResultMap和@Results不能单独同时存在,也就是说,如果当前dao层接口中只有单个查询方法,只能使用@Results。只有当出现两个及以上的查询方法时,才能出现@ResultMap去引用@Results映射(这一点就赶不上XML的灵活性了)。 |
@Options | 提供配置选项的附加值,它们通常在映射语句上作为附件功能配置出现。也就是一般和CRUD的注解一起出现,比如在XML中 |
@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层接口的扫描
持久层对象
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对象,这里一个无限递归的逻辑。
在使用注解的情况下,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的,像大于符号这些都有一些特定的写法。两种方式都应该掌握,具体应用那种方式,还得根据团队协商。