MyBatis
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
? Mybatis架构
? mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
? 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
? 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
? mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
? Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。
mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是Mappedstatement的id。
? MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
? MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
mybatis默认使用log4j作为输出日志信息
1 # Global logging configuration 2 log4j.rootLogger=DEBUG, stdout 3 # Console output... 4 log4j.appender.stdout=org.apache.log4j.ConsoleAppender 5 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 6 log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j.properties
? #{}和${}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换。#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
? parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中
? selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:org.apache.ibatis.exceptions.TooManyResultsException
selectList可以查询一条或多条记录。
? namespace
mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)settings(全局配置参数)typeAliases(类型别名)typeHandlers(类型处理器)objectFactory(对象工厂)plugins(插件)
environments(环境集合属性对象)environment(环境子属性对象)transactionManager(事务管理)dataSource(数据源)mappers(映射器)
- mappers(映射器)Mapper配置的几种方法:
- <mapper resource=" " /> 使用相对于类路径的资源(现在的使用方式) 如:<mapper resource="sqlmap/User.xml" />
- <mapper class=" " /> 使用mapper接口类路径 如:<mapper class="cn.tzy.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
- <package name=""/> 注册指定包下的所有mapper接口 如:<package name="cn.tzy.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
? mysql自增主键返回
1 <!-- 保存用户 --> 2 <insert id="saveUser" parameterType="com.tzy.mybatis.pojo.User"> 3 <!-- selectKey 标签实现主键返回 --> 4 <!-- keyColumn:主键对应的表中的哪一列 --> 5 <!-- keyProperty:主键对应的pojo中的哪一个属性 --> 6 <!-- order:设置在执行insert语句前执行查询id的sql,还是在执行insert语句之后执行查询id的sql --> 7 <!-- resultType:设置返回的id的类型 --> 8 <selectKey keyColumn="id" keyProperty="id" order="AFTER" resultType="int"> 10 SELECT LAST_INSERT_ID() 11 </selectKey> 12 INSERT INTO user 13 (username,birthday,sex,address) VALUES 14 (#{username},#{birthday},#{sex},#{address}) 15 </insert> 16 17 //LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。
? Mysql使用 uuid实现主键
1 <!-- 保存用户 --> 2 <insert id="saveUser" parameterType="com.tzy.mybatis.pojo.User"> 3 <!-- selectKey 标签实现主键返回 --> 4 <!-- keyColumn:主键对应的表中的哪一列 --> 5 <!-- keyProperty:主键对应的pojo中的哪一个属性 --> 6 <!-- order:设置在执行insert语句前执行查询id的sql,还是在执行insert语句之后执行查询id的sql --> 7 <!-- resultType:设置返回的id的类型 --> 8 <selectKey keyColumn="id" keyProperty="id" order="BEFORE" resultType="string"> 10 SELECT LAST_INSERT_ID() 11 </selectKey> 12 INSERT INTO `user` 13 (username,birthday,sex,address) VALUES 14 (#{username},#{birthday},#{sex},#{address}) 15 </insert> 16 17 //注意这里使用的order是“BEFORE”
? mybatis与hibernate不同
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
标签使用:
if标签
1 <!-- 根据条件查询用户 --> 2 <select id="queryUserByWhere" parameterType="user" resultType="user"> 3 SELECT id, username, birthday, sex, address FROM `user` 4 WHERE 1=1 5 <if test="sex != null and sex != ‘‘"> 6 AND sex = #{sex} 7 </if> 8 <if test="username != null and username != ‘‘"> 9 AND username LIKE 10 "%"#{username}"%" 11 </if> 12 </select> 13 14 //注意字符串类型的数据需要要做不等于空字符串校验。
where标签
上面的sql还有where 1=1 这样的语句,很麻烦
可以使用where标签进行改造
1 <!-- 根据条件查询用户 --> 2 <select id="queryUserByWhere" parameterType="user" resultType="user"> 3 SELECT id, username, birthday, sex, address FROM `user` 4 <!-- where标签可以自动添加where,同时处理sql语句中第一个and关键字 --> 5 <where> 6 <if test="sex != null"> 7 AND sex = #{sex} 8 </if> 9 <if test="username != null and username != ‘‘"> 10 AND username LIKE 11 ‘%${username}%‘ 12 </if> 13 </where> 14 </select>
? Sql片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。
把上面例子中的id, username, birthday, sex, address提取出来,作为sql片段,如下:
<!-- 根据条件查询用户 --> <select id="queryUserByWhere" parameterType="user" resultType="user"> <!-- SELECT id, username, birthday, sex, address FROM `user` --> <!-- 使用include标签加载sql片段;refid是sql片段id --> SELECT <include refid="userFields" /> FROM `user` <!-- where标签可以自动添加where关键字,同时处理sql语句中第一个and关键字 --> <where> <if test="sex != null"> AND sex = #{sex} </if> <if test="username != null and username != ‘‘"> AND username LIKE ‘%${username}%‘ </if> </where> </select> <!-- 声明sql片段 --> <sql id="userFields"> id, username, birthday, sex, address </sql>
如果要使用别的Mapper.xml配置的sql片段,可以在refid前面加上对应的Mapper.xml的namespace
? foreach标签
向sql传递数组或List,mybatis使用foreach解析,如下:
根据多个id查询用户信息
查询sql:
SELECT * FROM user WHERE id IN (1,10,24)
如下图在pojo中定义list属性ids存储多个用户id,并添加getter/setter方法
1 UserMapper.xml添加sql,如下: 2 <!-- 根据ids查询用户 --> 3 <select id="queryUserByIds" parameterType="queryVo" resultType="user"> 4 SELECT * FROM `user` 5 <where> 6 <!-- foreach标签,进行遍历 --> 7 <!-- collection:遍历的集合,这里是QueryVo的ids属性 --> 8 <!-- item:遍历的项目,可以随便写,,但是和后面的#{}里面要一致 --> 9 <!-- open:在前面添加的sql片段 --> 10 <!-- close:在结尾处添加的sql片段 --> 11 <!-- separator:指定遍历的元素之间使用的分隔符 --> 12 <foreach collection="ids" item="item" open="id IN (" close=")" separator=","> 14 #{item} 15 </foreach> 16 </where> 17 </select>
测试方法如下:
@Test public void testQueryUserByIds() { // mybatis和spring整合,整合之后,交给spring管理 SqlSession sqlSession = this.sqlSessionFactory.openSession(); // 创建Mapper接口的动态代理对象,整合之后,交给spring管理 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 使用userMapper执行根据条件查询用户 QueryVo queryVo = new QueryVo(); List<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(10); ids.add(24); queryVo.setIds(ids); List<User> list = userMapper.queryUserByIds(queryVo); for (User u : list) { System.out.println(u); } // mybatis和spring整合,整合之后,交给spring管理 sqlSession.close(); }
? 关联查询
商品订单数据模型
? 一对一查询
需求:查询所有订单信息,关联查询下单用户信息。
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单
? 方法一:使用resultType
使用resultType,改造订单pojo类,此pojo类中包括了订单信息和用户信息
这样返回对象的时候,mybatis自动把用户信息也注入进来了
? 改造pojo类
OrderUser类继承Order类后OrderUser类包括了Order类的所有字段,只需要定义用户的信息字段即可,如下图:
? Mapper.xml
在UserMapper.xml添加sql,如下
<!-- 查询订单,同时包含用户数据 --> <select id="queryOrderUser" resultType="orderUser"> SELECT o.id, o.user_id userId, o.number, o.createtime, o.note, u.username, u.address FROM `order` o LEFT JOIN `user` u ON o.user_id = u.id </select>
? Mapper接口
在UserMapper接口添加方法,如下图:
? 测试方法:
在UserMapperTest添加测试方法,如下:
1 @Test 2 public void testQueryOrderUser() { 3 // mybatis和spring整合,整合之后,交给spring管理 4 SqlSession sqlSession = this.sqlSessionFactory.openSession(); 5 // 创建Mapper接口的动态代理对象,整合之后,交给spring管理 6 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 7 8 // 使用userMapper执行根据条件查询用户 9 List<OrderUser> list = userMapper.queryOrderUser(); 10 11 for (OrderUser ou : list) { 12 System.out.println(ou); 13 } 14 15 // mybatis和spring整合,整合之后,交给spring管理 16 sqlSession.close(); 17 }
测试结果如下图:
定义专门的pojo类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。
? 方法二:使用resultMap
使用resultMap,定义专门的resultMap用于映射一对一查询结果。
? 改造pojo类
在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
改造Order如下图:
? Mapper.xml
这里resultMap指定orderUserResultMap,如下:
1 <resultMap type="order" id="orderUserResultMap"> 2 <id property="id" column="id" /> 3 <result property="userId" column="user_id" /> 4 <result property="number" column="number" /> 5 <result property="createtime" column="createtime" /> 6 <result property="note" column="note" /> 7 8 <!-- association :配置一对一属性 --> 9 <!-- property:order里面的User属性名 --> 10 <!-- javaType:属性类型 --> 11 <association property="user" javaType="user"> 12 <!-- id:声明主键,表示user_id是关联查询对象的唯一标识--> 13 <id property="id" column="user_id" /> 14 <result property="username" column="username" /> 15 <result property="address" column="address" /> 16 </association> 17 18 </resultMap> 19 20 <!-- 一对一关联,查询订单,订单内部包含用户属性 --> 21 <select id="queryOrderUserResultMap" resultMap="orderUserResultMap"> 22 SELECT 23 o.id, 24 o.user_id, 25 o.number, 26 o.createtime, 27 o.note, 28 u.username, 29 u.address 30 FROM 31 `order` o 32 LEFT JOIN `user` u ON o.user_id = u.id 33 </select>
? Mapper接口
编写UserMapper如下图:
? 测试方法
1 @Test 2 public void testQueryOrderUserResultMap() { 3 // mybatis和spring整合,整合之后,交给spring管理 4 SqlSession sqlSession = this.sqlSessionFactory.openSession(); 5 // 创建Mapper接口的动态代理对象,整合之后,交给spring管理 6 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 7 8 // 使用userMapper执行根据条件查询用户 9 List<Order> list = userMapper.queryOrderUserResultMap(); 10 11 for (Order o : list) { 12 System.out.println(o); 13 } 14 15 // mybatis和spring整合,整合之后,交给spring管理 16 sqlSession.close(); 17 }
测试效果如下图:
? 一对多查询
案例:查询所有用户信息及用户关联的订单信息。
用户信息和订单信息为一对多关系。
? 修改pojo类
在User类中加入List<Order> orders属性,如下图
? Mapper.xml
在UserMapper.xml添加sql,如下:
1 <resultMap type="user" id="userOrderResultMap"> 2 <id property="id" column="id" /> 3 <result property="username" column="username" /> 4 <result property="birthday" column="birthday" /> 5 <result property="sex" column="sex" /> 6 <result property="address" column="address" /> 7 8 <!-- 配置一对多的关系 --> 9 <collection property="orders" javaType="list" ofType="order"> 10 <!-- 配置主键,是关联Order的唯一标识 --> 11 <id property="id" column="oid" /> 12 <result property="number" column="number" /> 13 <result property="createtime" column="createtime" /> 14 <result property="note" column="note" /> 15 </collection> 16 </resultMap> 17 18 <!-- 一对多关联,查询订单同时查询该用户下的订单 --> 19 <select id="queryUserOrder" resultMap="userOrderResultMap"> 20 SELECT 21 u.id, 22 u.username, 23 u.birthday, 24 u.sex, 25 u.address, 26 o.id oid, 27 o.number, 28 o.createtime, 29 o.note 30 FROM 31 `user` u 32 LEFT JOIN `order` o ON u.id = o.user_id 33 </select>
? Mapper接口
? 测试方法
1 @Test 2 public void testQueryUserOrder() { 3 // mybatis和spring整合,整合之后,交给spring管理 4 SqlSession sqlSession = this.sqlSessionFactory.openSession(); 5 // 创建Mapper接口的动态代理对象,整合之后,交给spring管理 6 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 7 8 // 使用userMapper执行根据条件查询用户 9 List<User> list = userMapper.queryUserOrder(); 10 11 for (User u : list) { 12 System.out.println(u); 13 } 14 15 // mybatis和spring整合,整合之后,交给spring管理 16 sqlSession.close(); 17 }
测试效果如下图: