Mybatis的工作原理
-
读取Mybatis的配置文件:mybatis-config.xml为Mybatis的全局配置文件,配置了MyBatis的运行环境等信息。
-
加载映射文件,也就是Sql映射文件,该文件中配置了操作数据库的相关的Sql语句,需要在Mybatis配置文件mybatis-config.xml中加载,可以加载多个映射文件,每个文件对应数据库中的一张表
-
构造会话工厂:通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory
-
创建会话对象:由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法
-
Executor执行器:MyBatis底层定义了一个Executor接口来操作数据库,他将根据SqlSession传递的参数动态地生成需要执行的Sql语句,同时负责查询缓存的维护
-
MappedStatement对象:在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息
-
输入参数映射:映射类型可以是Map、List等集合类型,也可以是基本数据类型和pojo类型。类似于JDBC对preparedStatement对象设置参数的过程
-
输出结果映射:输出结果类型可以是Map、List等集合类型。也可以是基本数据类型和pojo类型。类似于JDBC对结果集的解析过程
Mybatis的工作原理
创建mapper.xml,书写响应的查询语句
创建mybatis-config.xml通知mybatis在Spring中的配置文件在哪
创建dao包,放入我们所要的接口@Repository和@Mapper(标注的接口自动装配为MyBatis的映射接口)
创建controller包,书写方法控制程序运行
创建applicationContext.xml文件配置整个的整合流程
-
注解扫描包
-
配置dataSource,使用的是dbcp线程池
-
属性依次是我们的驱动类名,url,用户名,密码
-
最大连接数maxTotal,最大空闲书manxIdle,初始化连接数
-
-
添加声明式事务,创建一个bean的id为txManager的事务管理器
-
开通事务注解将txManager配入
-
创建SqlSessionFactory,放入来源dataSource对象,同时告诉xml文件,mybatis文件的位置(数据源)
-
扫描mapper组件,将SqlSessionFactory放入
完成,搭载测试类即可
<!-- 扫描包注解 -->
<context:component-scan base-package="com.dao"></context:component-scan>
<context:component-scan base-package="com.controller"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/springtest?serverTimezone=UTC "></property>
<property name="username" value="root"></property>
<property name="password" value="cao236476"></property>
<!-- 最大连接数 -->
<property name="maxTotal" value="30"></property>
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10"></property>
<!-- 初始化连接数 -->
<property name="initialSize" value="5"></property>
</bean>
<!-- 添加声明式事务 -->
<!-- 声明 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 来源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- 配置MyBatis工厂,同时指定资源,并与Mybatis完美结合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- configLocation的属性值为Mybatis的核心配置文件 -->
<property name="configLocation" value="classpath:com/mybatis/mybatis-config.xml"></property>
</bean>
<!-- Mapper代理开发,使用Spring自动扫描mybatis的接口并装配
(Spring将指定包中@Mapper注解标注的接口自动装配为Mabatis的映射接口) -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" ></property>
</bean>
assocication元素属性分析:
-
property:指定映射到实体类的对象属性
-
column:指定表中的对应字段(即查询返回的列名)
-
javaType:指定映射到实体对象属性的类型
-
select:指定引入嵌套的子SQL语句,该属性用于关联映射中的嵌套查询
一对一级联查询的三种方法:
<mapper namespace="com.dao.PersonDao">
<!--
* property:指定映射到实体类的对象属性
* column:指定表中的对应字段(即查询返回的列名)
* javaType:指定映射到实体对象属性的类型
* select:指定引入嵌套的子SQL语句,该属性用于关联映射中的嵌套查询
id表示主键 result表示一般字段
-->
<!--
先查询的是person,后查询idcard表。这里查询的是Person对象,Person对象中包含卡的消息,也就是通过查询Person是没有办法
查询到Idcard对象的信息,所以使用resultMap输送数据,嵌套查询
对应的card,对应person中的idcard_id,查询Idcard对象需要通过selectCodeById来
-->
<!-- 建立一对一查询,级联查询第一种方法(嵌套查询,执行两个sql语句) -->
<resultMap type="com.mybatis.pojo.Person" id="cardAndPerson1">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="card" column="idcard_id" javaType="com.mybatis.pojo.Idcard"
select="com.dao.IdcardDao.selectCodeById">
</association>
</resultMap>
<!-- 这里才是主方法,查询的主方法,数据来自cardAndPerson -->
<select id="selectPersonById1" parameterType="Integer" resultMap="cardAndPerson1">
select * from person where id=#{id}
</select>
<!--
嵌套结果查询,先联合查询,得到结果之后,在将表中字段和idcard字段的结果结合起来
嵌套结果,组成最后的输出结果
-->
<!-- 一对一根据id查询个人信息,级联查询的第二种方法(嵌套结果,执行一个sql语句) -->
<resultMap type="com.mybatis.pojo.Person" id="cardAndPerson2">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一级联查询 直接映射到结果 -->
<association property="card" javaType="com.mybatis.pojo.Idcard">
<id property="id" column="idcard_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
<select id="selectPersonById2" parameterType="Integer" resultMap="cardAndPerson2">
select p.*,ic.code
from person p,idcard ic
where p.idcard_id = ic.id and p.id=#{id}
</select>
<!--
本次查询指定的SelectPersonById本身定义的时候就可以直接存储person+code
所以输出的结果是person+code
-->
<!-- 一对一根据id查询个人信息:连接查询(使用pojo存储结果) -->
<select id="selectPersonById3" parameterType="Integer" resultType="com.pojo.SelectPersonById">
select p.*,ic.code
from person p, idcard ic
where p.idcard_id = ic.id and p.id=#{id}
</select>
</mapper>
其中的查询的结果,idcard的结果都是通过@Mapper映射过去的。
不管是一对一还是一对多都有三种查询方式
-
嵌套查询(执行两次sql语句)
-
一对一:
-
使用的是<assocication property="" column="" javaType="" select=""/>
-
其中property是对应的映射到实体类的属性
-
column是指定表中的对应的字段(列名,此列名一般是作为查询property的数据传入)
-
javaType对应映射到实体类的属性的类型(property中配置的是它(类)的内容)
-
select指定引入嵌套的子SQL语句,该属性用于关联映射中的嵌套查询(用于第二次sql命令的执行)
-
sql语句是查询全部
-
-
一对多:
-
使用的是<collection property="" column="" ofType="" select=""/>
-
其中property是对应的映射到实体类的属性
-
column是指定表中的对应的字段(列名,此列名一般是作为查询property的数据传入)
-
ofType对应映射到实体类的属性的类型(property中配置的是它(类)的内容)
-
select指定引入嵌套的子SQL语句,该属性用于关联映射中的嵌套查询(用于第二次sql命令的执行)
-
sql语句是查询全部
-
-
-
嵌套结果(执行一次sql语句)
-
一对一:
-
使用的是<association property="" javaType=""/>
-
property是对应的映射到实体类的属性
-
javaType是对应的映射到实体类的属性的类型(propert隶属于这个类)
-
与一对一不同点,没有指定指定对应的表中的列名,采用了
-
<associcaton/><id property="" column=""/><result property="" column=""/>...(还有属性可以直接加result标签声明)<association>利用这个来直接映射到association的property中
-
sql语句是联合查询,根据指定的<assocication/><association>中配置的对应关系直接映射
-
-
一对多:
-
使用的是<collection property="" ofType=""/>
-
property是对应的映射到实体类的属性
-
ofType是对应的映射到实体类的属性的类型(propert隶属于这个类)
-
与一对一不同点,没有指定指定对应的表中的列名,采用了
-
<associcaton/><id property="" column=""/><result property="" column=""/>...(还有属性可以直接加result标签声明)<association>利用这个来直接映射到association的property中
-
sql语句是联合查询,根据指定的<collection/><collection>中配置的对应关系直接映射
-
-
-
使用pojo类存储结果(自定义一个pojo类存储需要的信息)
-
自定义一个类,类中的属性是查询信息中这个pojo类中所需要的,查询语句执行完之后,直接根据定义的pojo类直接映射输出
-
具体实现:下面有代码分析
<!-- 一对多查询 根据uid查询用户及其关联的订单信息:级联查询第一种方法(嵌套查询) -->
<resultMap type="com.mybatis.pojo.MyUser" id="userAndOrders1">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<!-- 一对多级联查询 ofType表示集合中的元素类型,将uid传递给selectOrdersById -->
<collection property="ordersList" column="uid" ofType="com.mybatis.pojo.Orders"
select="com.dao.OrdersDao.selectOrdersById"></collection>
</resultMap>
<select id="selectUserOrdersById1" parameterType="Integer" resultMap="userAndOrders1">
select * from user where uid=#{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:级联查询第二种方法(嵌套结果)-->
<resultMap type="com.mybatis.pojo.MyUser" id="userAndOrders2">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<collection property="ordersList" ofType="com.mybatis.pojo.Orders">
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
</collection>
</resultMap>
<select id="selectUserOrdersById2" parameterType="Integer" resultMap="userAndOrders2">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:连接查询,使用pojo类存储数据 -->
<select id="selectUserOrdersById3" parameterType="Integer" resultType="com.pojo.SelectUserOrdersById">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
三种方式的结果集
DEBUG [main] - ==> Preparing: select * from user where uid=? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 1 DEBUG [main] - ==> Preparing: select * from orders where user_id = ? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 2 MyUser [uid=1, uname=哈哈哈, usex=女, ordersList=[Orders [id=1, ordersn=123456], Orders [id=6, ordersn=654321]]] ============= DEBUG [main] - ==> Preparing: select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid = ? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 2 MyUser [uid=1, uname=哈哈哈, usex=女, ordersList=[Orders [id=1, ordersn=123456], Orders [id=6, ordersn=654321]]] ============== DEBUG [main] - ==> Preparing: select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 2 [SelectUserOrdersById [uid=1, uname=哈哈哈, usex=女, id=1, ordersn=123456], SelectUserOrdersById [uid=1, uname=哈哈哈, usex=女, id=6, ordersn=654321]] |
多对多的级联查询
场景:一个订单可以有多个产品,用产品细节表将这二者联合起来
建立orders_detail表,表中保存orders_id,product_id,需要查询的是订单中的产品数,一个订单中的产品是多个的,所以可以使用多对多级联查询,具体实现如下
<resultMap type="com.mybatis.pojo.Orders" id="ordersAndProduct">
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
<!-- 第三个属性提供了 所以使用一对多 -->
<collection property="products" ofType="com.mybatis.pojo.Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
<select id="selectOrdersProduct" parameterType="Integer" resultMap="ordersAndProduct">
select o.*,p.id as pid, p.name,p.price from orders o, orders_detail od, product p
where od.orders_id = o.id and od.product_id = p.id
</select>
这里需要注意的是在传值的时候,使用的别名也需要使用别名来传递,在上面的代码中,products中的id需要传入的是产品的id,可是产品的id被去了别名pid,所以在column中需要使用pid传入,以便映射传值,注意直接p.id传入,打印出的结果产品id会显示null
小结:
MyBatis实现查询时返回的结果集有哪几种常见的存储方式:
-
Map存储结果集,任何select语句都可以使用Map存储结果
-
使用pojo存储结果集(常用),因为使用pojo一方面可以使用自动映射,有resultType属性,更为复杂的级联,一对一,一对多,多对多等,需要使用resultMap属性配置映射集合。
MyBatis中如何给SQL语句传递参数?
利用#{参数名} 在parameterType是int时,sql语句中必须是#{},防注入
利用${参数名}是什么就输出什么
CRUD是 Create(创建)、Read(读取)、Update(更新)和Delete(删除)的缩写。
动态SQL
-
<if>元素
-
<choose>,<when>,<otherwise>元素,类似于switch
-
<trim>
-
trim元素中可以加上前缀prefix和后缀suffix
-
也可忽略前缀prefixOverrides和后缀suffixOverrides,可以设置前缀为where来代替where标签
-
-
<where>元素
-
使用标签不用写where,直接忽视第一条语句中的and
-
-
<set>元素
-
<set></set>中写判断条件以及要更新列名及值,后面写set条件where xx=xx
-
-
<foreach>元素
-
有多个属性
-
item:迭代的别名;index:指定一个名字,表示迭代过程中每次迭代到的位置
-
open表示语句以什么开始,separator表示每次迭代之间以什么符号作为分隔符
-
close表示语句以什么结束,最终要的是collection传入要执行的参数的类型(List array Map等)
-
-
<bind>元素
-
模糊查询,属性name表示执行sql所用的值来源(真实值),value表示满足条件的所有的模糊查询中获得的数据
-
部分标签例子:
<!-- 使用if元素根据条件动态查询用户信息 -->
<select id="selectUserByIf" parameterType="com.mybatis.pojo.MyUser" resultType="com.mybatis.pojo.MyUser">
select * from user where 1=1
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex!=null and usex!=''">
and usex=#{usex}
</if>
</select>
<!-- 使用choose元素 -->
<select id="selectUserByChoose" parameterType="com.mybatis.pojo.MyUser" resultType="com.mybatis.pojo.MyUser">
select * from user where 1=1
<choose>
<when test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</when>
<when test="usex!=null and usex!=''">
and usex=#{usex}
</when>
<otherwise>
and uid>10
</otherwise>
</choose>
</select>
<!-- 使用foreach元素 -->
<select id="selectUserByForeach" resultType="com.mybatis.pojo.MyUser" parameterType="List">
select * from user where uid in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
<!-- 使用bind模糊查询 -->
<select id="selectUserByBind" parameterType="com.mybatis.pojo.MyUser" resultType="com.mybatis.pojo.MyUser">
<bind name="paran_uname" value="'%'+uname+'%'"/>
select * from user where uname like #{paran_uname}
</select>