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>
来源:https://www.cnblogs.com/yangyuanhu/p/12127034.html