上篇文章我们说了MyBatis基础-----入门篇,讲解了MyBatis框架入门。CRUD案例以及基础用法等内容。这篇我们继续进行学习,MyBatis进阶----进阶篇。
回忆一下使用MyBatis的步骤:
- 在pom.xml中引入相关jar包
- 创建全局配置文件,包含数据库参数
- 创建实体类,数据库对应表
- 编写对应mapper文件
- 加载核心配置文件mybatis-config.xml
- 创建SqlSessionFactoryBuilder对象,通过.build方法创建会话工厂,读取流
- sqlSessionFactory.openSession创建sqlSession对象,执行sql语句。
(5-7步骤:
Resources.getResourceAsStream(“mybatis-config.xml”);加载核心配置文件得到InputStream------>
通过SqlSessionFactoryBuilder------>
创建sqlSessionFactoryBuilder对象------>
通过.build()读取InputStream创建sqlsessionFactory会话工厂------->
sqlSessionFactory.openSession();创建sqlSession对象------>
调用对应的方法执行sql语句。)
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
在上一次学习中,我们发现在使用mybatis通过session调用方法进行增删改查时,会因为sql标识符是字符串类型,且参数是object类型,例如在调用select方法时
这是sql的调用方无法确定sql标识符是否正确,也无法确定应该传递什么样的参数,从而影响了开发效率,易出错。
MyBatis如何解决?引出今天要写的内容
Mapper代理
直接利用session+id来执行sql的方式存在一些问题
- session执行sql时都需要提供要执行sql的id,而这个id是字符串类型,意味着id是否正确在编译期间是无法获知的,必须等到运行时才能发现错误,
- sql需要的参数和返回值类都不明确,这也增加了出错的概率
理想的状况:
像调用方法一样调用sql,既避免了直接写id的问题,也可以明确指定方法的参数类型和返回值类型
解决方案:
MyBatis通过动态代理来解决, 动态代理的意思就是自动产生一个实现类。
简单的说动态代理(动态生成),就是在运行过程中自动产生一个对象,用它来代理原本已经存在的对象的方法
。我们获得的这个mapper就是一个代理对象
MyBatis中本来由Executor(被代理对象)来完成sql的执行,现在由代理对象(自动生成)来代理Executor完成,代理对象会将我们的操作转交给Executor,由mapper代理去与session进行交互,同时屏蔽了容易出错的地方。
问题是:MyBatis怎么知道代理对象是什么样的对象呢?
这就需要为MyBatis提供Mapper接口,这个接口就是对mapper.xml中的sql语句的声明,与DAO层的接口一样,通过接口,我们可以明确的告诉sql的调用者,返回值类型,方法名称,参数分别是什么。
MyBatis其实就是获取执行的方法名称作为id来查找sql的,所以方法名和id必须完全一致
使用步骤
- 创建接口类
package com.cx.mapper;
import com.cx.pojo.Product;
import java.util.List;
public interface ProductMapper {
//根据id查询
Product selectById(int pid);
//根据name实现模糊查询
List<Product> selectByName(String pname);
}
- 提供响应的sql映射
<?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.cx.mapper.ProductMapper">
<!--根据id进行查询-->
<select id="selectById" parameterType="int" resultType="com.cx.pojo.Product">
select * from product where pid = #{pid}
</select>
<!--根据name进行模糊查询-->
<select id="selectByName" parameterType="String" resultType="com.cx.pojo.Product">
select * from product where pname like '%${新疆}%'
</select>
</mapper>
- 获取代理对象 执行方法完成操作
import com.cx.mapper.ProductMapper;
import com.cx.pojo.Product;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest2 {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
//加载核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//读取配置文件的流,创建sqlSessionFactory会话工厂
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}
@Test
public void test1(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
Product product = mapper.selectById(4);
System.out.println(product);
sqlSession.close();
}
@Test
public void test2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
List<Product> product = mapper.selectByName("新疆");
System.out.println(product);
sqlSession.close();
}
}
使用mapper代理注意事项
- 必须保证mapper.xml中的namespace与接口的全限定名称一致
- 方法的名称必须与对应的sql statement的id一致
- 方法的参数必须与对应的sql statement的parameterType一致
- 方法的返回值必须与对应的sql statement的resultType一致
案例总结如下:
mapper代理实现步骤:
- 创建接口,接口中的方法与sql对应
- mapper文件中的namespace指定对应的接口类
- 通过sqlsession.getMapper()来获取代理对象
XML配置
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
>configuration(配置)
>>properties(属性)
>>settings(设置)
>>typeAliases(类型别名)
>>typeHandlers(类型处理器)
>>objectFactory(对象工厂)
>>plugins(插件)
>>environments(环境配置)
>>>environment(环境变量)
>>>transactionManager(事务管理器)
>>>dataSource(数据源)
>>databaseIdProvider(数据库厂商标识)
>>mappers(映射器)
注意:>
代表层次结构,我们在书写的时候一档要遵循上面的结构偶进行,否则就会报错。
单独使用MyBatis的场景是很少的,后续都会将其与Spring进行整合,并由Spring来对MyBatis进行配置,下面讲解的为需要重点关注,其余的了解即可,若遇到特殊需求可查阅官方文档
属性(properties)
properties可从配置文件或是properties标签中读取需要的参数,使得配置文件各个部分更加独立
内部properties标签
<?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>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="***"/>
</properties>
<environments default="development">
<!-- 可配置多个环境 并指定默认使用的环境-->
<environment id="development">
<!-- 指定事务管理器-->
<transactionManager type="JDBC"/>
<!-- 指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!-- //表示本机 localhost &就是& xml中&需要转译-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 指定要加载的映射文件-->
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
为了更清晰的表达,标出变化之处:
外部配置文件
对于我们之前mybatis的mybatis-config.xml的文件,我们直接把jdbc相关参数配置到xml文件中去了,但是实际开发过程中数据量大,就会比较麻烦,我们可以建立一个单独的文件,进行存储,使用属性标签从文件中取值,此时就用到了外部配置文件
jdbc.properties位于resource下
在resource下建立一个jdbc.properties
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username = root
password = ***
mybatis-config.xml就可以改为:
<?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>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="***"/>
</properties>-->
<!--外部标签关联属性文件-->
<properties resource="jdbc.properties" />
<environments default="development">
<!-- 可配置多个环境 并指定默认使用的环境-->
<environment id="development">
<!-- 指定事务管理器-->
<transactionManager type="JDBC"/>
<!-- 指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!-- //表示本机 localhost &就是& xml中&需要转译-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 指定要加载的映射文件-->
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
当内部和外部属性出现同名时,则优先使用外部的;
别名(typeAliases)
typeAliases用于为Java的类型取别名,从而简化mapper中类名的书写
比如在mapper.xml中的resultType,要写包名加类名,书写不够简便。
注意typeAliases标签位于properties标签之下,书写顺序不能颠倒
为某个类定义别名
<!--为某个类定义别名-->
<typeAliases>
<!--type时全类名,alias取得时别名,可以省略,省略后默认的是类名,首字母不区分大小写-->
<typeAlias type="com.cx.pojo.Product" alias="prodcct" />
</typeAliases>
那么问题又来了,显示开发中,实体类会很多,这样会比较麻烦,我们可以直接扫描包,批量定义别名:
<!-- 批量定义别名-->
<typeAliases>
<!--扫描包,都设置为类名,首字母不区分大小写-->
<package name="com.cx.pojo"/>
</typeAliases>
上面两种定义别名的方式,用法一样,如下:
<?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.cx.mapper.ProductMapper">
<!--根据id进行查询-->
<select id="selectById" parameterType="int" resultType="Product">
select * from product where pid = #{pid}
</select>
<!--根据name进行模糊查询-->
<select id="selectByName" parameterType="String" resultType="product">
select * from product where pname like '%${新疆}%'
</select>
</mapper>
可以发现,resultType输出映射里面product实体类首字母大小写都可以。
别名冲突
使用package批量设置时很容易出现别名冲突,这是就需要使用@Alias
注解来为冲突的类单独设置别名
@Alias("products1")
public class Product {
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文件
以下有四种方式:
- 直接指定mapper文件
resource
指定资源文件的相对目录 相对于classpath maven项目会自动将java和resources都添加到classpath中
所以相对与resources来指定路径即可
<mappers>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
- 指定文件的绝对路径,MyBatis支持但是一般不用
url
<mappers>
<mapper url="file:///Users/jerry/Downloads/MYB/src/main/resources/mapper/ProductsMapper.xml"/>
</mappers>
- 通过指定接口 让MyBatis自动去查找对应的Mapper文件
class
这种方式要求映射文件和接口处于同一目录下,并且名称相同
要保证上述要求只需要在resources下创建于接口包名相同的目录即可
<mappers>
<mapper resource="<mapper class="com.cx.mapper.ProductMapper"/>
</mappers>
注意:运行前先clean,否则可能因为之前已经存在target而不会重新编译,导致无法加载新建的mapper文件
4.指定包名称,扫描包下所有接口和映射文件
这种方式同样要求映射文件和接口处于同一目录下,并且名称相同
<mappers>
<package name="com.cx.mapper"/>
</mappers>
注意:
上面方法3和方法4,
对于普通项目:
我们可以把mapper.xml 和 接口 放在一个目录下
但是对于maven项目:
我们可以把接口文件放在java下,把对应的mapper.xml放在recources下;
1.如果是单级目录,只需保证包名一致:
即在Java和recources下创建同名的文件夹mapper;
2.如果是多级目录此时注意如果要保持目录一致:
即在java我们可以创建时输入 一级目录.二级目录.三级目录 等;
但是在recources下不可以这样创建,应该使用“/” 即:一级目录/二级目录/三级目录
此处是因为在recources下会把“.”当成文件名的一部分,创建一个包;
而Java会当成分别包的符号,创建多级目录,虽然看着一样,但是结构不同。