Mybatis 基本应用
框架简介
三层架构
- 软件开发常用的架构是三层架构,之所以流行是因为有着清晰的任务划分。一般包括以下三层:
- 持久层:主要完成与数据库相关的操作,即对数据库的增删改查。
- 因为数据库访问的对象一般称为Data Access Object(简称DAO),所以有人把持久层叫做DAO层。
- 业务层:主要根据功能需求完成业务逻辑的定义和实现。 因为它主要是为上层提供服务的,所以有人把业务层叫做Service层或Business层。
- 表现层:主要完成与最终软件使用用户的交互,需要有交互界面(UI)。因此,有人把表现层称之为web层或View层。
- 三层架构之间调用关系为:表现层调用业务层,业务层调用持久层。
- 各层之间必然要进行数据交互,我们一般使用java实体对象来传递数据。
Mybatis简介
- MyBatis是一个优秀的基于ORM的半自动轻量级持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码
- mybatis 历史
- MyBatis 本是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投到GoogleCode旗下,iBatis正式改名为MyBatis ,代码于2013年11月迁移到Github
- Github地址:https://github.com/mybatis/mybatis-3/
Mybatis快速入门
MyBatis开发步骤 人狠话不多先上代码
环境搭建
<!--maven导入依赖 驱动就不写了-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
编写MyBatis核心文件
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--加载properties文件--> <properties resource="jdbc.properties"></properties> <!--设置别名--> <typeAliases> <!--方式一:给单个实体起别名--> <!-- <typeAlias type="com.lagou.domain.User" alias="user"></typeAlias>--> <!--方式二:批量起别名 别名就是类名,且不区分大小写--> <package name="com.lagou.domain"/> </typeAliases> <!--environments: 运行环境 可以配置多个 environment--> <environments default="development"> <environment id="development"> <!--当前的事务事务管理器是JDBC--> <transactionManager type="JDBC"></transactionManager> <!--数据源信息 POOLED:使用mybatis的连接池 UNPOOLED就是不使用连接池--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--引入映射配置文件--> <mappers> <mapper resource="mapper/UserMapper.xml"></mapper> </mappers> </configuration>
编写mapper映射
<mapper namespace="userMapper">
<!--namespace : 命名空间:与id属性共同构成唯一标识 namespace.id: user.findAll
resultType: 返回结果类型(自动映射封装):要封装的实体的全路径
-->
<!-- 查询所有 -->
<select id="findAll" resultType="uSeR">
select * from user
</select>
<!--新增用户-->
<!--#{} : mybatis中的占位符,等同于JDBC中的?
parameterType :指定接收到的参数类型 -->
<insert id="saveUser" parameterType="user">
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
<!--更新用户-->
<update id="updateUser" parameterType="user">
update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}
</update>
<!--删除用户 java.lang.Integer-->
<delete id="deleteUser" parameterType="int">
delete from user where id = #{abc}
</delete>
</mapper>
MyBatis常用配置解析
environments标签
- 数据库环境的配置,支持多环境配置
- 其中,事务管理器(transactionManager)类型有两种:
- JDBC: 这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
- MANAGED: 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。 例如:mybatis与spring整合后,事务交给spring容器管理。
- 其中,数据源(dataSource)常用类型有三种:
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
- JNDI : 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据 源,然后放置一个 JNDI 上下文的数据源引用
properties标签
实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的 properties:
typeAliases标签
- 类型别名是为 Java 类型设置一个短的名字。
- 为了简化映射文件 Java 类型设置,mybatis框架为我们设置好的一些常用的类型的别名:
- 原来的类型名称配置如下:
- 配置typeAliases,为com.lagou.domain.User定义别名为user:
-
<!--设置别名-->
<typeAliases>
<!--方式一:给单个实体起别名-->
<!-- <typeAlias type="com.lagou.domain.User" alias="user"></typeAlias>-->
<!--方式二:批量起别名 别名就是类名,且不区分大小写-->
<package name="com.lagou.domain"/>
</typeAliases>
mappers标签
- 该标签的作用是加载映射的,加载方式有如下几种:
-
使用相对于类路径的资源引用,例如:
<mapper resource="org/mybatis/builder/userMapper.xml"/>
使用完全限定资源定位符(URL),例如:
<mapper url="file:///var/mappers/userMapper.xml"/>
《下面两种mapper代理开发中使用:暂时了解》
使用映射器接口实现类的完全限定类名,例如
<mapper class="org.mybatis.builder.userMapper"/>
将包内的映射器接口实现全部注册为映射器,例如:
<package name="org.mybatis.builder"/>
其他标签就不多说了去官方看
Mybatis高级查询
ResutlMap属性
-
建立对象关系映射
- resultType 如果实体的属性名与表中字段名一致,将查询结果自动封装到实体类中
- ResutlMap 如果实体的属性名与表中字段名不一致,可以使用ResutlMap实现手动封装到实体类中
<resultMap id="orderMap" type="com.lagou.domain.Orders"> <id property="id" column="id"/> <result property="ordertime" column="ordertime"/> <result property="total" column="total"/> <result property="uid" column="uid"/> </resultMap> <select id="findAllWithUser" resultMap="orderMap"> SELECT * FROM orders </select>
多条件查询(三种)
方式一
- 使用 #{arg0}-#{argn} 或者 #{param1}-#{paramn} 获取参数
-
<!--多条件查询:方式一--> <select id="findByIdAndUsername1" resultMap="userResultMap" > <!-- select * from user where id = #{arg0} and username = #{arg1}--> select * from user where id = #{param1} and username = #{param2} </select>
方式二
- 使用注解,引入 @Param() 注解获取参数
-
/* 多条件查询方式二 */ public List<User> findByIdAndUsername2(@Param("id") int id, @Param("username") String username); <!--多条件查询:方式二--> <select id="findByIdAndUsername2" resultMap="userResultMap" > select * from user where id = #{id} and username = #{username} </select>
方式三(推荐)
- 使用pojo对象传递参数
-
/* 多条件查询方式三 */ public List<User> findByIdAndUsername3(User user); <!--多条件查询:方式三--> <select id="findByIdAndUsername3" resultMap="userResultMap" parameterType="com.lagou.domain.User"> select * from user where id = #{id} and username = #{usernameabc} </select>
模糊查询
<!--模糊查询:方式一-->
username传值要带%号
<select id="findByUsername" resultMap="userResultMap" parameterType="string">
<!-- #{}在mybatis中是占位符,引用参数值的时候会自动添加单引号 -->
select * from user where username like #{username}
</select>
<!--模糊查询:方式二-->
<select id="findByUsername2" resultMap="userResultMap" parameterType="string">
<!--parameterType是基本数据类型或者String的时候,${}里面的值只能写value
${}: sql原样拼接 有注入风险
-->
select * from user where username like '${value}'
</select>
- ${} 与 #{} 区别【笔试题】
- #{} :表示一个占位符号
- 通过 #{} 可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换
- #{}可以有效防止sql注入。
- #{} 可以接收简单类型值或pojo属性值。
- 如果parameterType传输单个简单类型值, #{} 括号中名称随便写。
- ${} :表示拼接sql串
- 通过 ${} 可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换,会出现sql注入问题。
- ${} 可以接收简单类型值或pojo属性值。
- 如果parameterType传输单个简单类型值, ${} 括号中只能是value。
- 补充:TextSqlNode.java 源码可以证明
- #{} :表示一个占位符号
Mybatis映射文件深入
返回主键
-
我们很多时候有这种需求,向数据库插入一条记录后,希望能立即拿到这条记录在数据库中的主键值
-
<!--添加用户:获取返回主键:方式一--> <!-- useGeneratedKeys: 声明返回主键 keyProperty:把返回主键的值,封装到实体中的那个属性上 --> <insert id="saveUser" parameterType="user" useGeneratedKeys="true" keyProperty="id"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> <!--添加用户:获取返回主键:方式二--> <insert id="saveUser2" parameterType="user" > <!-- selectKey : 适用范围更广,支持所有类型的数据库 order="AFTER" : 设置在sql语句执行前(后),执行此语句 keyColumn="id" : 指定主键对应列名 keyProperty="id":把返回主键的值,封装到实体中的那个属性上 resultType="int":指定主键类型 --> <selectKey order="AFTER" keyColumn="id" keyProperty="id" resultType="int"> SELECT LAST_INSERT_ID(); </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
-
主键返回是直接在 实体对象中
动态SQL
- 当我们要根据不同的条件,来执行不同的sql语句的时候,需要用到动态sql
动态 SQL 之
<!-- 动态sql之if : 多条件查询-->
<select id="findByIdAndUsernameIf" parameterType="user" resultType="com.lagou.domain.User">
select * from user
<!-- test里面写的就是表达式
<where>: 相当于where 1= 1,但是如果没有条件的话,不会拼接上where关键字
-->
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="username !=null">
and username = #{username}
</if>
</where>
</select>
动态 SQL 之
<!--动态sql之set : 动态更新-->
<update id="updateIf" parameterType="user">
update user
<!--<set> : 在更新的时候,会自动添加set关键字,还会去掉最后一个条件的逗号 -->
<set>
<if test="username != null">
username = #{username},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="sex != null">
sex = #{sex},
</if>
<if test="address != null">
address = #{address},
</if>
</set>
where id = #{id}
</update>
<sql id="selectUser">
select * from user
</sql>
动态 SQL 之
<!--动态sql的foreach标签:多值查询:根据多个id值查询用户-->
<select id="findByList" parameterType="list" resultType="user">
<include refid="selectUser"/>
<where>
<!--
collection : 代表要遍历的集合元素,通常写collection或者list
open : 代表语句的开始部分
close : 代表语句的结束部分
item : 代表遍历结合中的每个元素,生成的变量名
separator: 分隔符
-->
<foreach collection="collection" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
<!--动态sql的foreach标签:多值查询:根据多个id值查询用户-->
<select id="findByArray" parameterType="int" resultType="user">
<include refid="selectUser"/>
<where>
<!--
collection : 代表要遍历的集合元素,通常写collection或者list
open : 代表语句的开始部分
close : 代表语句的结束部分
item : 代表遍历结合中的每个元素,生成的变量名
separator: 分隔符
-->
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
SQL片段
- 映射文件中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的
-
<sql id="selectUser"> select * from user </sql> <!--动态sql的foreach标签:多值查询:根据多个id值查询用户--> <select id="findByList" parameterType="list" resultType="user"> <include refid="selectUser"/> 包含sql片段 <where> <!-- collection : 代表要遍历的集合元素,通常写collection或者list open : 代表语句的开始部分 close : 代表语句的结束部分 item : 代表遍历结合中的每个元素,生成的变量名 separator: 分隔符 --> <foreach collection="collection" open="id in (" close=")" item="id" separator=","> #{id} </foreach> </where> </select>
知识小结
- :查询
- :插入
- :修改
- :删除
- :返回主键
- :where条件
- :if判断
- :for循环
- :set设置
- :sql片段抽取
Mybatis核心配置文件深入
plugins标签
- MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
- 开发步骤:
- 导入通用PageHelper的坐标
- 在mybatis核心配置文件中配置PageHelper插件
- 测试分页数据获取
- 开发步骤:
-
maven pom.xml <!-- 分页助手 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency> sqlconfig.xml <plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <!--dialect: 指定方言 limit--> <property name="dialect" value="mysql"/> </plugin> </plugins> // 设置分页参数 // 参数1: 当前页 // 参数2: 每页显示的条数 PageHelper.startPage(1,2); List<User> users = mapper.findAllResultMap(); for (User user : users) { System.out.println(user); } // 获取分页相关的其他参数 PageInfo<User> pageInfo = new PageInfo<User>(users); System.out.println("总条数:"+pageInfo.getTotal()); System.out.println("总页数:"+pageInfo.getPages()); System.out.println("当前页:"+pageInfo.getPageNum()); System.out.println("每页显示长度:"+pageInfo.getPageSize()); System.out.println("是否第一页:"+pageInfo.isIsFirstPage()); System.out.println("是否最后一页:"+pageInfo.isIsLastPage())
知识小结
- MyBatis核心配置文件常用标签:
- properties标签:该标签可以加载外部的properties文件
- typeAliases标签:设置类型别名
- environments标签:数据源环境配置标签
- plugins标签:配置MyBatis的插件
Mybatis多表查询
一对一(多对一)
<!--一对一关联查询:查询所有订单,与此同时还要查询出每个订单所属的用户信息-->
<resultMap id="orderMap" type="com.lagou.domain.Orders">
<id property="id" column="id"/> 绑定主键
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<result property="uid" column="uid"/>
<!--
association : 在进行一对一关联查询配置时,使用association标签进行关联
property="user" :要封装实体的属性名
javaType="com.lagou.domain.User" 要封装的实体的属性类型
-->
<association property="user" javaType="com.lagou.domain.User">
<id property="id" column="uid"></id> 绑定主键 传值uid
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<select id="findAllWithUser" resultMap="orderMap">
SELECT * FROM orders o LEFT JOIN USER u ON o.uid = u.id
</select>
一对多
<!--一对多关联查询:查询所有的用户,同时还要查询出每个用户所关联的订单信息-->
<resultMap id="userMap" type="com.lagou.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--
collection : 一对多使用collection标签进行关联
property: User实体类中对应的成员变量
ofType:封装集合的泛型类型
-->
<collection property="ordersList" ofType="com.lagou.domain.Orders">
<id property="id" column="oid"></id>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<result property="uid" column="uid"/>
</collection>
</resultMap>
<select id="findAllWithOrder" resultMap="userMap">
SELECT u.*,o.id oid,o.ordertime,o.total,o.uid FROM orders o RIGHT JOIN USER u ON o.uid = u.id
</select>
多对多
<!--多对多关联查询:查询所有的用户,同时还要查询出每个用户所关联的角色信息-->
<resultMap id="userRoleMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="roleList" ofType="role">
<id column="rid" property="id"></id>
<result column="rolename" property="rolename"></result>
<result column="roleDesc" property="roleDesc"></result>
</collection>
</resultMap>
<select id="findAllWithRole" resultMap="userRoleMap">
SELECT u.*,r.id rid,r.rolename,r.roleDesc FROM USER u LEFT JOIN sys_user_role ur ON ur.userid = u.id
LEFT JOIN sys_role r ON ur.roleid = r.id
</select>
小结
- 多对一(一对一)配置:使用+做配置 *
- 一对多配置:使用+做配置 *
- 多对多配置:使用+做配置 *
- 多对多的配置跟一对多很相似,难度在于SQL语句的编写
MyBatis嵌套查询
- 嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一起。
-
联合查询 SELECT * FROM orders o LEFT JOIN USER u ON o.`uid`=u.`id`; 嵌套查询 先查询订单 SELECT * FROM orders 再根据订单uid外键,查询用户 SELECT * FROM `user` WHERE id = #{根据订单查询的uid} 最后使用mybatis,将以上二步嵌套起来
一对一嵌套查询
<resultMap id="orderMap2" type="com.lagou.domain.Orders">
<id property="id" column="id"/>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<result property="uid" column="uid"/>
<!--问题:1.怎么去执行第二条sql , 2.如何执行第二条sql的时候,把uid作为参数进行传递
column:uid
property:实体类对应成员变量
javaType 封装集合的泛型
fetchType 是否懒加载 eager立刻加载 lazy 懒加载
select查询语句
-->
<association property="user" javaType="com.lagou.domain.User"
select="com.lagou.mapper.UserMapper.findById" column="uid" fetchType="eager"/>
</resultMap>
<!--一对一嵌套查询-->
<select id="findAllWithUser2" resultMap="orderMap2">
SELECT * FROM orders
</select>
<!--根据id查询用户
useCache="true" 代表当前这个statement是使用二级缓存
-->
<select id="findById" resultType="com.lagou.domain.User" parameterType="int" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
一对多嵌套查询
<!--一对多嵌套查询:查询所有的用户,同时还要查询出每个用户所关联的订单信息-->
<resultMap id="userOrderMap" type="com.lagou.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--fetchType="lazy" : 延迟加载策略
fetchType="eager": 立即加载策略
-->
<collection property="ordersList" ofType="com.lagou.domain.Orders" column="id"
select="com.lagou.mapper.OrderMapper.findByUid" ></collection>
</resultMap>
<select id="findAllWithOrder2" resultMap="userOrderMap">
SELECT * FROM USER
</select>
<select id="findByUid" parameterType="int" resultType="com.lagou.domain.Orders">
SELECT * FROM orders WHERE uid = #{uid}
</select>
多对多嵌套查询
<resultMap id="userRoleMap2" type="com.lagou.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="roleList" ofType="com.lagou.domain.Role" column="id" select="com.lagou.mapper.RoleMapper.findByUid"></collection>
</resultMap>
<!--多对多嵌套查询:查询所有的用户,同时还要查询出每个用户所关联的角色信息-->
<select id="findAllWithRole2" resultMap="userRoleMap2">
SELECT * FROM USER
</select>
<select id="findByUid" resultType="com.lagou.domain.Role" parameterType="int">
SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON ur.roleid = r.id
WHERE ur.userid = #{uid}
</select>
小结
- 一对一配置:使用+做配置,通过column条件,执行select查询
- 一对多配置:使用+做配置,通过column条件,执行select查询
- 多对多配置:使用+做配置,通过column条件,执行select查询
- 优点:简化多表查询操作 缺点:执行多次sql语句,浪费数据库性能