Mybatis笔记(3)-基本crud、参数映射以及动态sql
基本CRUD以及参数映射
#{}和${}
默认情况下,使用 #{} 时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。
但PreparedStatement 的占位符不能用于一些元数据字段,如表名、列名。这时可以使用${},mybatis会直接插入一个不转义的字符串。
如要查询某个字段名为指定值的记录,不用在mapper中定义多个查询方法,如:findById()、findByUsername()。可以直接用一个方法:其中columnName指字段名,value指字段值
User selectByColumn(@Param("columnName") String columnName, @Param("value") String value);
映射文件中的sql语句:
<select id="selectByColumn" resultType="user">
select * from user where ${columnName}=#{value}
</select>
- 其中columnName将直接替换
${columnName}。
一般优先使用#{},其使用的是PreparedStatement会更加安全。而${}可能会引起SQL注入。
参数映射
-
对于方法形参到sql语句的参数映射:
- 当Mapper方法只有一个参数时,
#{}内的值无需与方法形参名对应,可以任意指定。若是有多个形参:- 以param加上形参在参数列表中的位置来命名(从1开始),比如:
#{param1}、#{param2}。 - 使用
@Param注解修饰形参,通过其value属性自定义命名,并在#{}内使用对应命名。
- 以param加上形参在参数列表中的位置来命名(从1开始),比如:
- 当传入的参数的一个JavaBean,要获取其属性,通过
#{属性名}即可。(若是bean中含bean,则使用#{innerBean.paramName}的形式) - parameterType属性可以指定传入的参数类型。该属性是可选的,一般情况下mybatis都可以推断出具体传入语句的参数类型。
- 当Mapper方法只有一个参数时,
-
对于sql语句到返回值的参数映射:
-
通过resultType属性可以指定返回值类型。且当返回值是集合时,返回类型不是设置为集合,而是应指定其集合元素的类型。resultType适用于表的字段名与JavaBean的成员变量名一一对应的情况,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。若是不对应将会抛出异常。这种情况可以使用
<resultMap>标签。PS:当使用
#{}从传入的对象中获取其属性值时是区分大小写的,如User对象有一个userName属性,若使用#{username},Mybatis将会找不到userName属性,抛出异常。 -
<resultMap>标签可以对表字段与JavaBean属性不对应的情况进行手动映射。且手动映射与自动映射会相互配合。即在<resultMapL>中可以只手动映射不对应的字段,那些对应的字段可以省略,Mybatis会进行自动映射处理。<resultMap>配置完毕后,在<select>等标签中通过resultMap属性引用。 -
返回类型的配置不能省略。
-
<resultMap>示例:
Book类:
public class Book {
private Integer id;
private String bookName;
private String bookAuthor;
private Integer price;
//getter、setter、toString
}
book表:

可看到Book类的bookName、bookAuthor属性与book表的book_name,author不对应。
映射文件的配置:
<resultMap>的id属性唯一标识该映射。- type属性指定要映射的JavaBean类;id标签用于映射主键,result标签用于映射普通字段。property属性指定JavaBean的属性名,column属性指定表的字段名。
- 在select标签中使用
resultMap="bookMap"引用配置的映射。
<resultMap id="bookMap" type="book">
<id property="bookName" column="book_name" />
<result property="bookAuthor" column="author" />
</resultMap>
<select id="selectById" resultMap="bookMap">
select * from book where id=#{id}
</select>
测试方法:
@Test
public void test() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(stream);
SqlSession session = factory.openSession();
BookMapper mapper = session.getMapper(BookMapper.class);
Book book = mapper.selectById(1);
System.out.println(book);
}
//输出:Book(id=1, bookName=Java疯狂讲义, bookAuthor=李刚, price=100)
select ★
下文例子中的user表:其中,id是自增主键

User类:
public class User {
private Integer id;
private String username;
private String password;
//...set、getter,toString
}
----------------
直接上例子:
UserMapper中查询相关方法定义:
//根据id查询
User selectById(Integer id);
//查询全部,返回类型是集合
List<User> selectAll();
//模糊查询。查询username中带有keyword的用户
List<User> selectByFuzzy(String keyword);
//根据指定字段和其字段值查询
User selectByColumn(String columnName, String value);
对应的映射文件:
<select id="selectById" resultType="user" >
select * from user where username = #{id}
</select>
<select id="selectAll" resultType="user">
select * from user
</select>
<select id="selectByFuzzy" resultType="user">
select * from user where username like #{keyword}
</select>
<select id="selectByColumn" resultType="user">
select * from user where ${param1}=#{param2}
</select>
要注意的点:
- select标签中的id属性指定的是Mapper中的方法名。(对后文的
<insert>等标签一样) - resultType属性指定返回值类型。例子中的user指User类,已使用别名。
- 注意selectAll方法的返回值的List集合,而对应select标签的resultType属性是List的元素User。
- selectByColumn是根据字段名跟字段值进行查询。与上文例子相同。该mapper方法有两个参数,在sql语句中使用param+形参位置的形式映射参数。
- 在测试方法中,调用mapper的selectByFuzzy方法时,传入的参数是
%小%。即进行模糊查询时,要注意传参时先拼接好通配符。
测试方法:
测试类中使用了Junit的@Before初始化了SqlSessionFactory,并作为测试类的成员变量:
private SqlSessionFactory factory;
@Before
public void initFactory() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
factory = builder.build(in);
}
@Test
public void testSelect(){
try(SqlSession session = factory.openSession();){
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(2);
System.out.println("id为2的User: " + user);
List<User> users = mapper.selectAll();
System.out.println("所有User: ");
for (User u : users){
System.out.println(u);
}
//查询用户名中带有‘小’的用户
List<User> users2 = mapper.selectByFuzzy("%小%");
System.out.println("用户名中带有 小 的用户:");
for(User u : users2){
System.out.println(u);
}
User user1 = mapper.selectByColumn("username", "张三");
System.out.println("查询username字段为张三的记录:" + user1);
}
}
/**
输出:
id为2的User: User(id=2, username=李四, password=5896596)
所有User:
User(id=1, username=张三, password=123456)
User(id=2, username=李四, password=5896596)
User(id=3, username=王小强, password=666666)
User(id=4, username=许小红, password=777777)
用户名中带有 小 的用户:
User(id=3, username=王小强, password=666666)
User(id=4, username=许小红, password=777777)
查询username字段为张三的记录:User(id=1, username=张三, password=123456)
*/
insert ★
同上,直接看例子。
Mapper方法定义:
void insert(User user);
对应的映射文件配置:
<insert id="insert" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into user values(null, #{username}, #{password})
</insert>
测试方法:
@Test
public void testInsert(){
try(SqlSession sqlSession=factory.openSession()){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("蜘蛛侠");
user.setPassword("555969");
mapper.insert(user);
System.out.println("执行插入后,获取user的id值:" + user.getId());
//提交事务
sqlSession.commit();
}
}
//输出:执行插入后,获取user的id值:11
要注意的点:
-
注意到sql语句并没有插入id值(id是自增主键),可以通过
useGeneratedKeys="true" keyProperty="id"的配置,获取执行插入操作后新记录的主键值,获取的主键值会返回给传入的JavaBean;keyProperty指定的是表的字段名,,如例子中执行插入操作后,把插入记录的id值赋值给user对象。-
也可以通过
<selectKey>标签实现:(order属性值表示插入后或插入前获取主键)<insert id="insert" parameterType="user"> <selectKey keyProperty="id" order="AFTER" resultType="int"> select LAST_INSERT_ID(); </selectKey> insert into user values(null, #{username}, #{password}) </insert>
-
-
在测试方法的最后,有一句
sqlSession.commit();,mybatis默认开启事务,即autoCommit属性为false,所以执行插入操作后需要提交事务。也可以在获取sqlSession时指定autoCommit属性为true,如下:SqlSession sqlSession=factory.openSession(true)(更新、删除操作也一样)否则操作将会回滚。
delete & update
这两个元素与insert大同小异。看个简单示例即可。
Mapper方法:
void update(User user);
void deleteById(Integer id);
映射文件:
<update id="update">
update user set username=#{username}, password=#{password} where id = #{id}
</update>
<delete id="deleteById">
delete from user where id=#{id}
</delete>
测试方法:
@Test
public void testUpdate(){
try(SqlSession session = factory.openSession(true)){
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setId(3);
user.setUsername("王宝强");
user.setPassword("666888");
mapper.update(user);
System.out.println("修改了id为3的user");
}
}
@Test
public void testDelete(){
try(SqlSession session = factory.openSession(true)){
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteById(14);
System.out.println("删除了id为14的user");
}
}
抽取可复用sql语句
通过<sql>标签定义可重用的sql语句片段,通过<include>标签引用。
官方文档的示例:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
动态SQL
Mybatis的动态sql可以使用简单的配置来实现根据条件拼接sql语句。让开发者可以不用过多关注于语法方面的问题。
下文例子中的数据表(employee):
对应的JavaBean:
public class Employee {
private Integer id;
private String name;
private String gender;
private String department;
private Integer salary;
//...set getter toString
}
if
EmployeeMapper中的方法:
List<Employee> selectByConditionIf(Employee employee);
对应的映射文件:
<select id="selectByConditionIf" resultType="employee">
select * from employee where gender=#{gender}
<if test="name != null">and name like #{name}</if>
<if test="salary != null">and salary>#{salary}</if>
</select>
-
test属性设置了条件。当employee的相关属性不为空时,将会在where中添加对应条件。test属性中要使用传入参数的属性时,直接使用属性名即可。
-
where关键字后必须带有一个条件,否则当所有if条件不满足时,sql语句将会出现语法错误:
select * from employee where。 -
若是test中的条件要用到与或非,不能使用
&& ||等符号,应使用and not or。如author != null and author.name != null。 -
当所有条件都不满足,就只有
gender=#{gender}一个条件。
测试方法:
@Test
public void testIf(){
try(SqlSession session = factory.openSession()){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setGender("男");
employee.setName("%小%");
employee.setSalary(1000);
List<Employee> employees = mapper.selectByConditionIf(employee);
for(Employee e : employees){
System.out.println(e);
}
}
}
//输出:
//Employee(id=1, name=小明, gender=男, department=保安部, salary=5000)
//Employee(id=3, name=小强, gender=男, department=保安部, salary=5000)
choose
<choose> 类似于Java的switch,即按顺序判断条件,若满足则拼接对应的语句,之后的条件不再判断。所有条件都不满时,则拼接<otherwise>中的语句。
示例:
EmployeeMapper中的方法:
List<Employee> selectByConditionChoose(Employee employee);
对应的映射文件:
<select id="selectByConditionChoose" resultType="employee">
select * from employee where 1=1
<choose>
<when test="name != null">and name like #{name}</when>
<when test="salary != null">and salary>#{salary}</when>
<otherwise>
and gender=#{gender}
</otherwise>
</choose>
</select>
where
相比于使用if,使用<where>标签在sql语句中不用出现where关键字。where元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。若子句的开头为 “AND” 或 “OR”,where元素也会将它们去除。
示例:
Mapper方法:
List<Employee> selectByConditionWhere(Employee employee);
映射文件:
<select id="selectByConditionWhere" resultType="employee">
select * from employee
<where>
<if test="gender != null">and gender=#{gender}</if>
<if test="name != null">and name like #{name}</if>
<if test="salary != null">and salary>#{salary}</if>
</where>
</select>
当有条件满足时,mybatis会插入对应where子句。若没有条件满足,则会查询所有记录。
set
与<where>类似,可通过<set> 实现动态更新语句。
示例:
Mapper方法:
void updateByCondition(Employee employee);
映射文件:
<update id="updateByCondition">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="department != null">department = #{department},</if>
<if test="salary != null">salary = #{salary}</if>
</set>
where id = #{id}
</update>
测试方法:
@Test
public void testUpdateByCondition(){
//注意以开启事务的方式获取SqlSession
try(SqlSession session = factory.openSession(true)){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setId(1);
employee.setDepartment("程序员");
employee.setSalary(15000);
mapper.updateByCondition(employee);
}
}
执行测试方法,id为1的记录的department字段和salary字段被更新。
- 注意if元素里的sql语句尾部的逗号不可少(最后一个if可省略逗号),否则会发送语法错误。
- 当所有的if都不满足条件会发生语法错误。
foreach
<foreach> 可以通过循环的方式获取集合中的值。尤其适合IN语句。
根据id集合查询的示例:
Mapper方法:
List<Employee> selectByIdsWithForeach(List<Integer> ids);
映射文件:
<select id="selectByIdsWithForeach" resultType="employee">
select * from employee
where id in
<foreach collection="collection" index="index" item="id"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
测试方法:
@Test
public void testSelectByIdsWithForeach(){
try(SqlSession session = factory.openSession()){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
ArrayList<Integer> ids = new ArrayList<>();
//获取id为1、2、3的记录
ids.add(1);
ids.add(2);
ids.add(3);
List<Employee> employees = mapper.selectByIdsWithForeach(ids);
for(Employee e : employees){
System.out.println(e);
}
}
}
<foreach> 的collection属性的属性值只能为list和collection,使用别的命名比如ids会抛出以下异常:
Cause: org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [collection, list]
从异常来看应该是只能使用这两种命名....
且Mapper方法的集合形参的参数名不用跟collection属性值相对应。

浙公网安备 33010602011771号