MyBatis进阶
mapper代理
中直接利用session+id来执行sql的方式存在一些问题
- session执行sql时都需要提供要执行sql的id,而这个id是字符串类型,意味着id是否正确在编译期间是无法获知的,必须等到运行时才能发现错误,
- sql需要的参数和返回值类都不明确,这也增加了出错的概率
理想的状况:像调用方法一样调用sql,既避免了直接写id的问题,也可以明确指定方法的参数类型和返回值类型
解决方案:
mybatis通过动态代理来解决,简单的说动态代理(动态生成),就是在运行过程中自动产生一个对象,用它来代理原本已经存在的对象的方法
mybatis中本来由executor(被代理对象)来完成sql的执行,现在由代理对象(自动生成)来代理executor完成,代理对象会将我们的操作转交给executor
问题是:mybatis怎么知道代理对象是什么样的对象呢?,这就需要为mybatis提供mapper接口,这个接口就是对mapper.xml中的sql语句的声明,与dao层的接口一毛一样
使用步骤
-
创建接口类
package com.kkb.mapper; import com.kkb.pojo.products; public interface productsmapper { //根据name查询一个products public products selectproductbyname(string name); }
-
提供响应的sql映射
<mapper namespace="com.kkb.mapper.productsmapper"> <select id="selectproductbyname" parametertype="string" resulttype="com.kkb.pojo.products"> select *from products where pname = #{name} </select> </mapper>
-
获取代理对象 执行方法完成操作
@test public void proxytest(){ sqlsession session = factory.opensession(); productsmapper mapper = session.getmapper(productsmapper.class); products product = mapper.selectproductbyname("泰国咖喱"); system.out.println(product); session.close(); }
注意事项:
- 必须保证mapper.xml中的namespace与接口的全限定名称一致
- 方法的名称必须与对应的sql statement的id一致
- 方法的参数必须与对应的sql statement的parametertype一致
- 方法的返回值必须与对应的sql statement的resulttype一致
xml配置
mybatis 的配置文件包含了会深深影响 mybatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typealiases(类型别名)
- typehandlers(类型处理器)
- objectfactory(对象工厂)
- plugins(插件)
-
environments(环境配置)
- environment(环境变量)
- transactionmanager(事务管理器)
- datasource(数据源)
- environment(环境变量)
- databaseidprovider(数据库厂商标识)
- mappers(映射器)
注意配置文件各个节点个层次是固定,需按照上述的顺序书写否则报错,
单独使用mybatis的场景是很少的,后续都会将其与spring进行整合,并由spring来对mybatis进行配置,加粗的为需要重点关注,其余的了解即可,若遇到特殊需求可查阅官方文档
属性(properties)
properties可从配置文件或是properties标签中读取需要的参数,使得配置文件各个部分更加独立
内部properties标签
外部配置文件
jdbc.properties位于resource下
driver = com.mysql.cj.jdbc.driver url = jdbc:mysql:///mybatisdb?servertimezone=asia/shanghai&characterencoding=utf8 user = root password = admin
引用方式
当内部和外部属性出现同名时,则优先使用外部的;
别名(typealiases)
typealiases用于为java的类型取别名,从而简化mapper中类名的书写
为某个类定义别名
在mapper.xml中就可以直接使用别名 不区分大小写
当指定package时将批量为包下所有类指定别名为类名小写
<typealiases> <package name="com.kkb.pojo"/> </typealiases>
使用package批量设置时很容易出现别名冲突,这是就需要使用@alias注解来为冲突的类单独设置别名
@alias("products1") public class products { private int pid; private string pname; private float price; private date pdate; private string cid; .....}
下面列出mybatis已存在的别名
_byte | byte |
---|---|
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | string |
byte | byte |
long | long |
short | short |
int | integer |
integer | integer |
double | double |
float | float |
boolean | boolean |
date | date |
decimal | bigdecimal |
bigdecimal | bigdecimal |
object | object |
map | map |
hashmap | hashmap |
list | list |
arraylist | arraylist |
collection | collection |
iterator | iterator |
映射(mappers)
在mapper.xml文件中定义sql语句后,就必须让mybatis知道,到哪里去找这些定义好的sql,这就需要在配置文件中指出要加载的mapper的位置;mybatis支持4种方式来加载mapper文件
<mappers> <!-- 1.指定资源文件的相对目录 相对于classpath maven项目会自动将java和resources都添加到classpath中 所以相对与resources来指定路径即可--> <mapper resource="mapper/productsmapper.xml"/> <!-- 2.指定文件的绝对路径,mybatis支持但是一般不用--> <mapper url="file:///users/jerry/downloads/myb/src/main/resources/mapper/productsmapper.xml"/> <!-- 3.通过指定接口 让mybatis自动去查找对应的mapper 这种方式要求映射文件和接口处于同一目录下,并且名称相同 要保证上述要求只需要在resources下创建于接口包名相同的目录即可 注意:运行前先clean,否则可能因为之前已经存在target而不会重新编译,导致无法加载新建的mapper文件 --> <mapper class="com.kkb.mapper.productsmapper"/> <!-- 4.指定包名称,扫描包下所有接口和映射文件 这种方式同样要求映射文件和接口处于同一目录下,并且名称相同--> <package name="com.kkb.mapper"/> </mappers>
第3,4种方式映射文件目录示例:
动态sql
动态sql指的是sql语句不是固定死的,可以根据某些条件而发生相应的变化,这样的需求非常多见
例如:页面提供的搜索功能,要根据用户给出的一个或多个条件进行查询
在jdbc时代,我们通过java代码来判断某个条件是否有效然后拼接sql语句,这是非常繁琐的,mybatis的动态sql很好的解决了这个问题,使判断逻辑变得简洁直观;
在mapper中通过标签来完成动态sql的生成
1. if
从页面接受参数后我们会将参数打包为对象,然后将对象作为参数传给mybatis执行查询操作,sql语句需要根据是否存在参数而动态的生成
mapper:
<!-- 根据姓名或cid进行搜索--> <select id="searchproducts" parametertype="products" resulttype="products"> select *from products where 1=1 <if test="pname != null"> and pname like '%${pname}%' </if> <if test="cid != null"> and cid = #{cid} </if> </select>
测试:
@test public void searchtest(){ sqlsession session = factory.opensession(); productsmapper mapper = session.getmapper(productsmapper.class); //查询条件对象 products condition = new products(); condition.setname("*"); condition.setcid("s001"); //执行查询 list<products> product = mapper.searchproducts(condition); system.out.println(product); session.close(); }
where1=1
用于保持sql的正确性,当不存在条件时,则查询全部
2.where
where的作用就是用于取出上面的where 1=1,因为这会让人看起来产生疑惑,其作用是将内部语句中的第一个and去除
<select id="searchproducts" parametertype="products" resulttype="products"> select *from products <where> <if test="pname != null"> and pname like '%${pname}%' </if> <if test="cid != null"> and cid = #{cid} </if> </where> </select>
3. foreach
当一个条件中中需要需要多个参数时则需要将多个参数拼接到一起,例如: in, not in
mapper:
<select id="searchproducts" parametertype="products" resulttype="products"> select *from products <where> <if test="pname != null"> and pname like '%${pname}%' </if> <if test="cid != null"> and cid = #{cid} </if> <if test="ids != null"> <foreach collection="ids" open="and pid in (" close=")" separator="," item="id" index="i"> #{id} </foreach> </if> </where> </select> <!-- <if test="ids != null"> 这里不仅判断属性是否为空还判断集合中是否有元素 foreache 标签属性说明: 强调:动态sql本质就是在拼接字符串,带着自己拼接sql的思路来编写动态sql会更好理解 collection 要遍历的集合 open 拼接的前缀 close 拼接的后缀 separator 拼接元素之间的分隔符 item 遍历得到的临时变量名 index 当前元素的索引(不常用) -->
测试代码:
@test public void searchtest(){ sqlsession session = factory.opensession(); productsmapper mapper = session.getmapper(productsmapper.class); //查询条件对象 products condition = new products(); int[] ids = new int[]{1,2,3,4,5,}; condition.setids(ids); //执行查询 list<products> product = mapper.searchproducts(condition); system.out.println(product); session.close(); }
注意需要为pojo(products
)对象增加ids属性
4. set
set标签用于更新语句,当同事要更新多个字段时,我们需要留意当前是否是最后一个set,避免在后面出现,
符号,使用set标签后可自动去除最后的逗号
mapper:
<update id="updateproducttest" parametertype="products"> update products <set> <if test="pname != null and pname != ''"> pname = #{pname}, </if> <if test="price != null and price > 0"> price = #{price}, </if> <if test="pdate != null"> pdate = #{pdate}, </if> <if test="cid != null and cid != ''"> cid = #{cid}, </if> </set> where pid = #{pid} </update>
测试代码:
@test public void updatetest2(){ sqlsession session = factory.opensession(); productsmapper mapper = session.getmapper(productsmapper.class); //获取已有对象 products product = mapper.selectproductbyid(7); product.setpname("云南小土豆"); product.setprice(10.5f); //执行更新 mapper.updateproducttest(product); system.out.println(product); session.commit(); session.close(); }
5. sql与include
sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。 例如可以吧sql语句中的字段列表提取出来作为通用的sql片段。然后在sql语句中使用 include 节点引用这个sql片段。
<!--提取片段--> <sql id="fields">pid,pname,price,cid</sql> <select id="includetest" resulttype="products"> select <include refid="fields"/> <!-- 引用片段--> from products </select>
高级映射
在一些情况下数据库的记录和pojo对象无法直接映射,包括两种情形:
- 数据库字段与pojo字段名称不同(可以避免);
- 关联查询时,需要将关联表的数据映射为另一个类型的pojo(一对一),或list中(一对多);
在mybatis中通过resultmap来完成自定义映射
1.自定义字段与属性映射
先将products表中的字段更名为p_xxx,如下所示:
mapper:
<!--自定义映射关系 id:该映射关系的标识 type:映射到的pojo类型此处为别名--> <resultmap id="product_resultmap" type="products"> <!--主键--> <id column="p_id" property="pid"/> <!--其他字段--> <result column="p_name" property="pname"/> <result column="p_price" property="price"/> <result column="p_date" property="pdate"/> <result column="p_cid" property="cid"/> </resultmap> <!--引用映射关系--> <select id="selectproductscustommapping" resultmap="product_resultmap"> select *from products </select>
2. 关联查询
2.1 关联关系
两个表之间记录的对应关系,分为一对一和一对多,而多堆多则是三张表之间的关系,若掌握了两张表之间的一对多关系的处理,则多堆多也就不是问题了,因为本质上多对多就是两个一对多组成的
案例表:
这两张表之间存在一对一和一对多
站在user表角度来看 一个用户可能对应多个订单即一对一
站在order表角度来看 一个订单只能对应一个用户即一对多
pojo类:
order.java
import java.util.date; public class order { private int id,user_id; private string number; private date createtime; private string note; private user user; //get/set.... }
user.java
import java.util.date; import java.util.list; public class user { private int id; private string name; private date birthday; private string sex,address; private list<order> orders; public user() { } //get/set.... }
2.2 一对一映射
需求:根据订单编号查询订单信息以及用户名称和地址
sql语句为:
select o.*,u.username,u.address from orders o join kuser u on o.user_id = u.id where o.id = 8; #要查询的字段根据需求来定,这里只需要查询用户的姓名和地址
ordersmapper.xml:
<!--自定义映射--> <resultmap id="order_resultmap" type="com.kkb.pojo.order"> <!--orders表映射--> <id column="id" property="id"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!--关联的user表映射--> <association property="user" javatype="user"> <id column="user_id" property="id"/> <result column="username" property="name"/> <result column="address" property="address"/> </association> </resultmap> <select id="selectorderbyid" parametertype="int" resultmap="order_resultmap"> select o.*,u.username,u.address from orders o join kuser u on o.user_id = u.id where o.id = #{oid}; </select>
测试代码:
@test public void test(){ sqlsession session = factory.opensession(); ordersmapper mapper = session.getmapper(ordersmapper.class);//记得提供映射接口 order order = mapper.selectorderbyid(8); system.out.println(order); session.close(); }
补充:当连接查询出现重复字段时如:两个表都有id字段,mybatis简单的取第一个匹配的字段值,很多时候这是不正确的,我们可以给这个重复的字段取个别名来避免,像这样:
<select id="selectorderbyid" parametertype="int" resultmap="order_resultmap"> select u.*,o.*,o.id oid from orders o join kuser u on o.user_id = u.id where o.id = #{oid}; </select>
理所当然的 在resultmap中则使用oid来进行映射
<id column="oid" property="id"/>
2.3 一对多映射
需求:根据用户编号查询用户信息以及用户所有订单信息
sql语句为:
select u.*,o.*,o.id oid from kuser u left join orders o on o.user_id = u.id where u.id = 1;
usermapper.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.kkb.mapper.usermapper"> <!--自定义映射--> <resultmap id="user_resultmap" type="user" automapping="true"> <result column="username" property="name"/> <collection property="orders" oftype="order" automapping="true"> <result column="oid" property="id"/> </collection> </resultmap> <select id="selectuserbyid" parametertype="int" resultmap="user_resultmap"> select u.*,o.*,o.id oid from kuser u left join orders o on o.user_id = u.id where u.id = #{uid} </select> </mapper>
automapping="true" 将自动映射字段名与属性名能对应的字段,我们只需要添加对应不上的即可
测试代码:
@test public void test2(){ sqlsession session = factory.opensession(); usermapper mapper = session.getmapper(usermapper.class); user user = mapper.selectuserbyid(1); system.out.println(user); session.close(); }
注意:无论如何在collection标签中必须至少存在一个手动映射字段否则,将不会合并重复的主记录(user) 按照官方的说法,建议手动映射id字段,可提高整体性能:去看看
另外collection标签中 oftype用于指定元素的类型 javatype指定容器类型
2.4嵌套映射,select
当关联查询非常复杂时,可以用嵌套的select,其原理是在映射复杂数据时执行另一个select来完成
<resultmap id="order_resultmap2" type="order" automapping="true"> <id column="id" property="id"/> <!-- 指定嵌套查询 column是传给内层查询的参数 --> <association property="user" column="user_id" select="selectuserbyuid" javatype="user"/> </resultmap> <!-- 外层查询--> <select id="selectorderbyid2" parametertype="int" resultmap="order_resultmap2"> select * from orders where id = #{id} </select> <!-- 嵌套查询--> <select id="selectuserbyuid" parametertype="int" resulttype="user"> select *from kuser where id = #{id} </select>
这种方式同样适用于一对多的关联关系
<resultmap id="blogresult" type="blog"> <collection property="posts" javatype="arraylist" column="id" oftype="post" select="selectpostsforblog"/> </resultmap> <select id="selectblog" resultmap="blogresult"> select * from blog where id = #{id} </select> <select id="selectpostsforblog" resulttype="post"> select * from post where blog_id = #{id} </select>
上一篇: JVM中的动态语言支持简介
下一篇: 补铁吃什么好,这些食物少不了