MyBatis核心之映射文件编写 一、MyBatis的开发环境介绍 本次案例中使用的还是上一节说的表t_user
及其对应的实体类User
,有必要先说明User类中的方法都附有打印信息以供理解MyBatis的运行原理,同时为了方便我们写了一个简单的工具类SqlSessionUtil
:
1 2 3 4 5 6 7 8 9 10 11 public class SqlSessionUtil { public static SqlSession getSqlSession () { try { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory factory = new SqlSessionFactoryBuilder ().build(inputStream); return factory.openSession(true ); } catch (IOException e) { throw new RuntimeException (e); } } }
二、MyBatis的参数解析 1.测试方法 我们先来看一下UserMapper
接口,其中包含了参数解析的若干个测试方法,我们下面一一查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public interface UserMapper { List<User> getAllUsers () ; User getUserById (Integer id) ; User checkUser (String username, String password) ; User checkUserByMap (Map<String, Object> map) ; int insertUser (User user) ; User checkUserByParam (@Param("username") String username, @Param("password") String password) ; }
2.语句编写 重点内容是对应的UserMapper.xml
如何书写,我们先给出答案,后面我们会逐个解释并测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.huling.mapper.UserMapper" > <select id ="getAllUsers" resultType ="user" > select * from t_user; </select > <select id ="getUserById" resultType ="user" > select * from t_user where id = #{id} </select > <select id ="checkUser" resultType ="user" > select * from t_user where username = '${param1}' and password = '${param2}' </select > <select id ="checkUserByMap" resultType ="user" > select * from t_user where username = #{username} and password = #{password} </select > <insert id ="insertUser" > insert into t_user values (null, #{username}, #{password}, #{age}, #{sex}, #{a}) </insert > <select id ="checkUserByParam" resultType ="user" > select * from t_user where username = #{username} and password = #{password} </select > </mapper >
3.答案解析 对于getAllUsers
方法来说,直接查询出t_user
表中的所有记录并转换为Java中的User
对象,这都是通过无参构造函数+Setter方法完成的,前面一节已经说过这里不多阐述,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test1 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); List<User> users = mapper.getAllUsers(); users.forEach(System.out::println); } }
对于getUserById
方法来说,SQL语句中获取方法参数不仅可以使用#{id}
,任何有效字符例如#{aaa}
、#{x}
都可以,不过为了便于理解和维护,我们还是建议见名知意,最好使用方法参数名id
(其实我们通过Param注解强制参数的键为id
),测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test2 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.getUserById(2 ); System.out.println(user); } }
对于checkUser
方法来说,需要两个方法参数,这两个参数的名称只能是[arg0, arg1, param1, param2]
,其他任何名称都不行(其实我们建议通过Param注解强制参数的键为参数名username
和password
),测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test2 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User tom = mapper.checkUser("tom" , "123456" ); System.out.println(tom); }
对于checkUserByMap
方法来说,不需要MyBatis帮我们把参数对封装为Map结构供参数解析使用,直接使用传入的Map对象即可,因此也只能使用Map对象的键作为参数,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void test2 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); Map<String, Object> map = new HashMap <>(); map.put("username" , "tom" ); map.put("password" , "123456" ); User tom = mapper.checkUserByMap(map); System.out.println(tom); }
对于insertUser
方法来说,方法参数是实体类对象的属性
,注意这里的属性并不是指实体类的某个字段,而是Getter
方法表示的属性(首字母小写),例如下面User类具有属性a
:
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void test2 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); int result = mapper.insertUser(new User (null , "admin" , "123456" , 22 , "男" , "admin@163.com" )); System.out.println("result = " + result); } }
对于checkUserByParam
方法来说,方法参数均通过Param注解强制标注其参数名称,这也是我们以后推荐的方式,有利于理解和维护,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test2 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User tom = mapper.checkUserByParam("tom" , "123456" ); System.out.println(tom); } }
4.最佳实践
如果方法中包含若干个简单参数,则使用@Param
注解写明参数名称
如果方法中包含Map类型参数,则按照Map的键名称获取参数(通过get方法)
如果方法中包含实体类类型参数,则按照实体类的属性名称获取参数(通过Getter方法)
三、MyBatis的返回值封装 1.测试方法 我们先来看一下SelectMapper
接口,其中包含了返回值封装的若干个测试方法,我们下面一一查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface SelectMapper { User getUserById (@Param("id") Integer id) ; List<User> getAllUsers () ; int getCount () ; Map<String, Object> getUserByIdToMap (@Param("id") Integer id) ; List<Map<String, Object>> getAllUsersToMap () ; }
2.语句编写 重点内容是对应的SelectMapper.xml
如何书写,我们先给出答案,后面我们会逐个解释并测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.huling.mapper.SelectMapper" > <select id ="getUserById" resultType ="user" > select * from t_user where id = #{id} </select > <select id ="getAllUsers" resultType ="user" > select * from t_user; </select > <select id ="getCount" resultType ="_int" > select count(*) from t_user; </select > <select id ="getUserByIdToMap" resultType ="map" > select * from t_user where id = #{id} </select > <select id ="getAllUsersToMap" resultType ="map" > select * from t_user; </select > </mapper >
3.答案解析 对于getUserById
方法来说,需要返回单个User对象,这里直接使用resultType
是因为数据库的表字段与实体类的字段名一致,不需要我们额外的映射处理工作,MyBatis会直接使用实体类的Setter
方法完成返回值的封装,测试代码如下:
1 2 3 4 5 6 7 8 9 10 @Test public void test3 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SelectMapper mapper = session.getMapper(SelectMapper.class); System.out.println(mapper.getUserById(2 )); } }
对于getAllUsers
方法来说,需要返回多个User对象,因此使用List类型,其他跟上面的方法没什么不同,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test3 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SelectMapper mapper = session.getMapper(SelectMapper.class); List<User> users = mapper.getAllUsers(); users.forEach(System.out::println); } }
对于getCount
方法来说,返回Java内建的基本数据类型int,我们在前面XML配置一节中说到MyBatis默认已经配置好了很多Java内建类型的别名,例如对于基本数据类型int的别名就是_int
、Map的别名就是map
等等,测试代码如下:
1 2 3 4 5 6 7 8 9 10 @Test public void test3 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SelectMapper mapper = session.getMapper(SelectMapper.class); System.out.println(mapper.getCount()); } }
对于getUserByIdToMap
方法来说,查询的每一行记录会被封装为一个Map对象,其中记录的字段名为Map的键,记录的字段值为Map的值,这在日常开发场景下也是经常使用的,因为对于没有实体类与查询结果匹配的方法,采用Map不妨是一种不错的手段,测试代码如下:
1 2 3 4 5 6 7 8 9 10 @Test public void test3 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SelectMapper mapper = session.getMapper(SelectMapper.class); System.out.println(mapper.getUserByIdToMap(4 )); } }
对于getAllUsersToMap
方法来说,跟上面的getUserByIdToMap
类似,只不过是返回多行记录,因此使用List类型,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test3 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SelectMapper mapper = session.getMapper(SelectMapper.class); List<Map<String, Object>> mapList = mapper.getAllUsersToMap(); mapList.forEach(System.out::println); } }
4.最佳实践
如果返回值是简单类型,需要根据MyBatis的默认别名填写在resultType
标签中
如果返回值是实体类类型,根据数据库表记录字段名与实体类属性名是否一致、是否存在多对一或者一对多关系等决定是否采用resultMap
如果返回值是Map类型,直接使用SQL查询语句的字段名作为键,字段值作为值形成Map
对象返回,对于复杂查询例如多表联查时经常会使用到
四、MyBatis的特殊注意 1.测试方法 我们先来看一下SQLMapper
接口,其中包含了需要特殊注意的若干个测试方法,我们下面一一查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface SQLMapper { List<User> getUsersLike (@Param("username") String username) ; int deleteMore (@Param("ids") String ids) ; List<User> getUsersByTableName (@Param("tableName") String tableName) ; void insertUser (User user) ; }
2.语句编写 重点内容是对应的SQLMapper.xml
如何书写,我们先给出答案,后面我们会逐个解释并测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.huling.mapper.SQLMapper" > <select id ="getUsersLike" resultType ="User" > select * from t_user where username like "%"#{username}"%" </select > <delete id ="deleteMore" > delete from t_user where id in (#{ids}) </delete > <select id ="getUsersByTableName" resultType ="User" > select * from ${tableName} </select > <insert id ="insertUser" useGeneratedKeys ="true" keyProperty ="id" > insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email}) </insert > </mapper >
3.答案解析 对于getUsersLike
方法来说,如果我们想要使用模糊查询,此时不能使用'%#{username}%'
的参数表示方式,只能使用'%${username}%'
、concat('%',#{username},'%)
或者"%"#{username}"%"
这几种方式,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test4 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SQLMapper mapper = session.getMapper(SQLMapper.class); List<User> users = mapper.getUsersLike("a" ); System.out.println(users); } }
对于deleteMore
方法来说,方法参数是用户ID的字符串例如”1,2,3”,我们不能使用#{}
,因为这会多出单引号,因此我们只能使用${}
,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test4 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SQLMapper mapper = session.getMapper(SQLMapper.class); int result = mapper.deleteMore("6,7,8" ); System.out.println("result = " + result); } }
将#{ids}
改为${ids}
后,发现批量删除成功:
对于getUsersByTableName
方法来说,表名同样不能加单引号,因此不能使用#{},测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test4 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SQLMapper mapper = session.getMapper(SQLMapper.class); List<User> tUser = mapper.getUsersByTableName("t_user" ); System.out.println(tUser); } }
对于insertUser
方法来说,如果我们想获取插入数据的自增主键值,我们需要设置useGeneratedKeys
标签为true,并且设置keyProperty
属性为传入的实体类对象参数的主键id
,这样插入成功后可以通过查看user对象的id属性获得自增主键值,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test4 () { try (SqlSession session = SqlSessionUtil.getSqlSession()) { SQLMapper mapper = session.getMapper(SQLMapper.class); User user = new User (null , "huling" , "123456" , 22 , "男" , "huling@163.com" ); mapper.insertUser(user); System.out.println(user); } }
实际上,设置useGeneratedKeys
标签为true后,这会令MyBatis使用JDBC的getGeneratedKeys
方法来取出插入成功后由数据库内部生成的主键值,上图的打印顺序也说明了这个问题。