

MyBatis spring howto
source link: https://zzyongx.github.io/blogs/mybatis-spring-howto.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1 Mapper 的定义
Mapper是开发者关注最多的部分,先看看它是如何定义的。
public interface EmployeeMapper { class Sql { final static String TABLE = "employee"; final static String SELECT = "SELECT * FROM " + TABLE + " WHERE id = #{id}"; final static String SELECT_ALL = "SELECT * FROM " + TABLE + " ORDER BY id DESC LIMIT #{limit}"; final static String SELECT_PAGE = "SELECT * FROM " + TABLE + " WHERE id < #{id} ORDER BY id LIMIT #{limit}"; final static String DELETE = "DELETE FROM " + TABLE + " WHERE id = #{id}"; public static String selectByName(Map<String, Object> param) { SQL sql = new SQL().SELECT("*").FROM(TABLE) .WHERE("name = #{name}"); Employee.Gender gender = (Employee.Gender) param.get("gender"); if (gender != null) { sql.AND().WHERE("gender = #{gender}"); } return sql.toString(); } public static String insert(Employee employee) { SQL sql = new SQL() .INSERT_INTO(TABLE) .VALUES("name", "#{name}") .VALUES("phone", "#{phone}"); if (employee.getDepartment() != null) { sql.VALUES("department", "#{department}"); } if (employee.getPosition() != null) { sql.VALUES("position", "#{position}"); } if (employee.getGender() != null) { sql.VALUES("gender", "#{gender}"); } return sql.toString(); } public static String update(Employee employee) { SQL sql = new SQL().UPDATE(TABLE); if (employee.getName() != null) { sql.SET("name = #{name}"); } if (employee.getDepartment() != null) { sql.SET("department = #{department}"); } if (employee.getPosition() != null) { sql.SET("position = #{position}"); } if (employee.getGender() != null) { sql.SET("gender = #{gender}"); } if (employee.getPhone() != Integer.MIN_VALUE) { sql.SET("phone = #{phone}"); } return sql.WHERE("id = #{id}").toString(); } } @Select(Sql.SELECT_ALL) List<Employee> findAll(long limit); @Select(Sql.SELECT_PAGE) List<Employee> findPager(@Param("id") long id, @Param("limit") long limit); @SelectProvider(type = Sql.class, method = "selectByName") List<Map<String, Object>> findByName(@Param("name") String name, @Param("gender") Employee.Gender gender); @Select(Sql.SELECT) Employee find(@Param("id") long id); @InsertProvider(type = Sql.class, method = "insert") @Options(useGeneratedKeys=true, keyProperty = "id") int add(Employee employee); @UpdateProvider(type = Sql.class, method = "update") int update(Employee employee); @Delete(Sql.DELETE) int delete(long id); }
首先 EmployeeMapper 是个接口,其次它的每一个方法都和一个SQL,或者一个生成SQL的方法关联。这里用了嵌套类EmployeeMapper.Sql只是为了分离SQL和SQL对应的方法。
1.1 从方法到SQL
这里通过注解将方法和其对应的SQL关联起来,注解很多,这里列出常用的。
注意 Mapper中方法不能重载
注意 MySQL类型 BOOLEAN 实际是 tinyint(1) ,对应于Java的类型也是 Boolean
1.1.1 通过 Select Insert Update Delete 注解绑定静态SQL
如 findAll delete
所示,这里要求SQL都是编译时常量。静态SQL简单,但是缺乏一定的灵活性。
1.1.2 通过 SelectProvider InsertProvider UpdateProvider DeleteProvider 注解绑定动态SQL
如 insert update
所示,通过type指定产生SQL的类,通过method指定类的方法。
1.1.3 通过 Options 注解可以指定额外的选项
这里useGeneratedKeys
指出需要获取自增ID, keyProperty
指定自增ID对应的字段名
1.2 从方法的参数到SQL的参数
Mapper中SQL的参数如何表示呢? 使用这样的形式 #{name}
,表示此处是一个参数,它的名字是 name
。显然我们需要把传递给方法的参数,和SQL中的参数绑定起来。当查询返回时,还需要把查询结果和方法的返回值绑定。绑定关系一旦确定,还需要 JavaType
和 Java.sql.Types
的类型转换,使用所谓的 TypeHandler ,这个后面单独说。
注意 方法的参数不能为null,如果可能为null,需要用动态SQL判断一下。
1.2.1 单个简单参数
参数类型是long,String,并且只有一个。此时SQL需要绑定变量时,只能绑定它。
1.2.2 多个简单参数
此时需要通过 Param
注解来标记变量对应的SQL变量的名字。例如 findByName(@Param("name") String name, @Param("gender") Employee.Gender gender)
指定了 name 绑定到 #{name} gender 绑定到 #{gender} 。
注意 动态生成的SQL的函数,只接受一个参数。EmployeeMapper.Sql 中的所有函数都只有一个参数,这里有个小转换。例如:EmployeeMapper.findByName 有两个参数,但是它的动态SQL函数 EmployeeMapper.Sql.selectByName 却只接受一个函数,此时MyBatis会把 EmployeeMapper.findByName 的两个参数转成一个Map,Map的key是Param注解的值。
1.2.3 对象参数
如果SQL需要 #{name} 对应的值时,会调用对象的 getName 方法。如果有 public 的 name 字段也可以。
1.2.4 Map参数
如果SQL需要 #{name} 时,调用Map的 get(Object key) 方法。
1.2.5 查询返回值
如果查询返回一行,那么返回值可以是一个Map或者一个对象。返回的数据行绑定到Map,行的列对应Map的Key,数据类型是String,列值对应Map的Value,数据类型由默认的TypeHandler决定。返回的数据绑定到对象,和对象参数的绑定方法一样。
如何返回多行,返回值是一个Map或者对象的List。
如果增删改SQL,那么返回值是int,返回影响的行数。
注意 返回Map和返回对象有一点不同。返回对象时,知道 JavaType
和 java.sql.Types
,所以TypeHandler的作用更大,但是返回Map时,没有预定义的 JavaType
。例如:对象写入数据库时,假设把enum转成了int,那么从数据库读数据时,如果返回Map,读到int,并不知道需要转成enum。
1.3 区分三类数据值
- null 值, null 是没有值的意思,值没有设置。基本类型int,long没有null,推荐使用
MIN_VALUE
当null。 - 默认值,当值为 null 时采用的值,可以没有。
- 设置的值。
区分这三个值的意义在于,如果是读操作,如果有默认值的话,那么可以给 null 值一个默认值。如果是写操作,那么 null 值意味着不要更改数据库的值。
1.4 动态SQL
动态SQL的构建函数(method)只是构建SQL,值绑定并不是发生在这个阶段。但是在这个阶段显式绑定也没有太大问题。例如 sql.SET("name = #{name}")
写成 sql.SET("name = " + employee.getName())
也没有什么不可以,除了可能引发SQL注入。
1.5 SQL注入
客户端请求SQL服务器执行SQL有两种方式。一种是请求最终的SQL,例如 SELECT * FROM employee WHERE id = 10
。一种是先请求 SELECT * FROM employee WHERE id = #{id}
,然后发送 id 对应的具体值过去,这被称作 Prepare Execute 两个阶段。
前者更直观,也是SQL注入的地方。因为10是用户输入的,如果缺少过滤,用户可能输入 10; drop table employee ,这样最终的SQL语句是 SELECT * FROM employee WHERE id = 10; drop table employee
, drop 语句被注入了。
后者没有这个问题,后者执行 id 为 10; drop table employee 的查询。虽然可以防止SQL注入,但是并不能预防持久型XSS攻击,适当的输入过滤有时还是必要的。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK