MyBatis 的真正强大在于它的语句映射,它指导着 Mybatis 如何进行数据库的增删改查。在之前的demo当中已简单使用过,写sql的那个XML 文件就是映射文件。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.pingguo.bloomtest.dao.UserMapper"> <select id="getUserById" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where id = #{id} </select> </mapper>
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
不过,先来看下增删改查。
一、增删改查速览
我继续在接口里新增几个方法(查询已有):
public interface UserMapper { // 查询 User getUserById(Integer id); // 新增 void addUser(User user); // 修改 void updateUser(User user); // 删除 void deleteUser(Long id); }
在映射文件UserMapper.xml
中,使用insert
、update
、delete
标签来写对应方法的sql:
<!--新增--> <insert id="addUser"> insert into user(username, password, createTime, updateTime) values(#{username}, #{password}, #{createTime}, #{updateTime}) </insert> <!--更新--> <update id="updateUser"> update user set username=#{username}, password=#{password}, createTime=#{createTime}, updateTime=#{updateTime} where id=#{id} </update> <!--删除--> <delete id="deleteUser"> delete from user where id=#{id} </delete>
另外,mybatis 允许增删改直接定义如下的返回值:Integer、Long、Boolean
。那么接口里定义的方法就可以写成这样:
public interface UserMapper { // 查询 User getUserById(Integer id); // 新增 boolean addUser(User user); // 修改 boolean updateUser(User user); // 删除 boolean deleteUser(Long id); }
拿addUser
方法来说,新增成功之后返回的就是true
。
在测试类里可以测试下上面几个方法,这里贴出来测试一下新增:
@Test void test3() throws IOException{ SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); // 获取到的 SqlSession 不会自动提交数据 SqlSession session = sqlSessionFactory.openSession(); User newUser = new User(); newUser.setUsername("新用户1"); newUser.setPassword("111111"); newUser.setCreateTime(new Date()); newUser.setUpdateTime(new Date()); try { UserMapper userMapper = session.getMapper(UserMapper.class); userMapper.addUser(newUser); // 需要这里手动提交 session.commit(); } finally { session.close(); } }
注意这里sqlSessionFactory.openSession()
不会自动提交数据,需要session.commit()
手动提交。如果需要自动提交,里面传入true
即可:
sqlSessionFactory.openSession(true)
新增成功。
其他几个也成功通过测试。
二、insert获取自增主键的值
mysql 的自增主键,mybatis 也可以获取到,只需要添加一个属性useGeneratedKeys
,默认是false
。
那获取到的主键值,可以通过keyProperty
将这个值封装给 Javabean 的某个属性,比如User
类中的id
。
<!--新增--> <insert id="addUser" useGeneratedKeys="true" keyProperty="id"> insert into user(username, password, createTime, updateTime) values(#{username}, #{password}, #{createTime}, #{updateTime}) </insert>
可以在之前的测试方法里加一个打印,看下获取到的主键值:
成功获取到主键值为 12 。
查看数据库表里新增多数据主键就是 12 。
三、单个参数、多个参数、对象
1. 单个参数
拿上面根据 id 进行查询为例:
<select id="getUserById" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where id = #{id} </select>
使用#{参数名}
,mybatis就会正确取出参数值。
当只有一个参数的时候,mybatis 不会做特殊处理,比如sql中的where条件是根据id
来查询,但我就算写#{abc}
,也正常查询。
2. 多个参数
在接口UserMapper
里再定义一个新的方法,里面传入多个参数:
// 查询 使用多个参数 User getUserByIdAndUsername(Integer id, String username);
对应的增加sql映射:
<select id="getUserByIdAndUsername" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where id = #{id} and username=#{username} </select>
测试一下,可以正常查询。
@Test void test2params() throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); System.out.println(userMapper.getUserByIdAndUsername(3, "大周")); }
3. 对象
如果多个参数正好是我们业务逻辑的数据模型,那可以直接传入对象,比如POJO,通过#{属性名}
取出属性值。
如果多个参数没有对应的POJO,为了方便,也可以传入 Map 。
在 UserMapper 里增加方法:
User getUserByMap(Map<String, Object> map);
sql 映射文件增加对应配置:
<select id="getUserByMap" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where id = #{id} and username=#{username} </select>
新增个测试方法,查询正常。
@Test void testByMap() throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); map.put("id", 3); map.put("username", "大周"); System.out.println(userMapper.getUserByMap(map)); }
如果上述这种 map 还要经常使用,推荐编写一个TO(Transfer Object)数据传输对象。
四、$ 和 # 取值
mybatis 中是采用#{}
和${}
都是可以取值的,但是两者还是有区别的,比如:
<select id="getUserByIdAndUsername" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where id = ${id} and username=#{username} </select>
我在这里把2个参数分别用了不同的方法来取值:${id}
和#{username}
,然后执行之前的测试函数,看下打印结果。
区别就是:
#{}
,是以预编译形式,讲参数设置到 sql 语句中${}
,取出的值直接拼装在 sql 语句中,会存在一定的安全问题
大多数情况下,我们都去使用#{}
。
不过在某些情况下,还是会用到${}
。比如一个工资表按照年份进行了拆分,表名前面是年份,那么要动态查询表的时候,年份就是个变量。
表名是不能预编译的,所以不能使用#{}
。对于这种原生JDBC不支持占位符的地方,就可以使用${}
。
select * from ${year}_salary where xxx;
再比如,用到排序,我想把排序的条件和升序降序可以动态的穿进来,也可以使用${}
:
select * user order by ${age} ${desc_order};
五、select 元素
1. select 返回 list
如果我定义了一个查询,返回的是一个 list。
List<User> getUserByUsernameLike(String username);
sql 映射文件这样写:
<select id="getUserByLastNameLike" resultType="com.pingguo.bloomtest.pojo.User" databaseId="mysql"> select * from user where username like #{username} </select>
注意这里如果返回的是一个集合类型,resultType
里要写集合中的类型。
2. select 返回 map
如果需要返回一条记录的 map,key 就是列名,value 就是对应的值。
Map<String, Object> getUserByIdReturnMap(Integer id);
sql 映射文件这样写:
<select id="getUserByIdReturnMap" resultType="map"> select * from user where id = #{id} </select>
测试下,查询正常。
如果需要封装多条记录比如Map<Integer, User>
,key 是记录的主键,value 是记录封装后的 javabean 。
Map<Integer, User> getUserByUsernameReturnMap(String username);
sql 映射文件:
<select id="getUserByUsernameReturnMap" resultType="com.pingguo.bloomtest.pojo.User"> select * from user where username like #{username} </select>
这里的返回类型还是 User,现在 value 有了,那么如何把主键作为 map 的 key 呢?
使用注解@MapKey("id")
,告诉 mybatis 使用哪个属性作为 key:
@MapKey("id") Map<Integer, User> getUserByUsernameReturnMap(String username);
测试一下:
如果换成其他属性作为 key 也是可以的。