Mybatis
-
认识mybatis:
- 官方语言:Mybatis是一款优秀的持久层框架,它支持定制SQL,存储过程,以及高级映射.Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集.Mybatis可以使用简单的XML或注解来配置和映射原生类型,接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- 简单来说,Mybatis就是一个操作数据库,用来增删改查的工具.
- Mybatis是一个半自动的ORM框架 : 什么叫半自动呢,就是可以帮你生成一部分的SQL,主要优点就是支持定制化SQL,这个特性让他能够进行SQL调优
- 适用场景: 在业务频繁修改的系统,需要进行SQL调优的系统中使用
- 国内互联网公司用Mybatis的比较多
-
数据库建表:
-
既然是需要进行增删改查操作,那么我们先来建表:
-- 商家表 create table business( business_id int not null auto_increment primary key, business_name VARCHAR(255) not null UNIQUE KEY, business_password VARCHAR(255) not null ); -- 商品表 create table produce( produce_id int not null auto_increment primary key, produce_name VARCHAR(255) not null, produce_price DOUBLE not null, produce_number int not null ); -- 新增测试数据 insert into business VALUES(default,"li","wohenshuai"); insert into business VALUES(default,"superli","wohenqiang");
-
-
新建实体类:
/** * 商家类,字段最好与数据库向对应,否则需要自己转. * 驼峰命名可以配置mybatis代转 */ @Data public class Business { private int businessId; private String bussinessName; private String bussinessPassword; } /** * 商品类 */ @Data public class Produce { private int produceId; private String produceName; private String producePrice; private String produceNumber; }
-
使用JDBC查询:
public class BusinessDaoImp {
public Business findBusinessByBusinessId(int businessId) throws ClassNotFoundException, SQLException {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/liwithmybatis","root","123456");
// 编写sql
String sql="select * from business where business_id=?";
// 获取预编译对象
PreparedStatement pr = connection.prepareStatement(sql);
// 填充数据
pr.setInt(1,businessId);
// 执行查询操作,获得结果集
ResultSet resultSet = pr.executeQuery();
Business business = new Business();
if (resultSet.next()){
int id = resultSet.getInt("business_id");
String name = resultSet.getString("business_name");
String psw = resultSet.getString("business_password");
business.setBusinessId(id);
business.setBussinessName(name);
business.setBussinessPassword(psw);
}
return business;
}
}
-
使用mybatis查询
-
首先当然是加依赖啦,建一个Maven项目,不熟悉Maven的朋友请去看我前面的博客记录.在pom.xml添加数据库相关依赖和Mybatis的依赖:
<!-- mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!--mybatis依赖--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency>
-
配置:Mybatis的主配置:在resources文件夹下面新建一个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> <!-- Mybatis的setting配置,这些去官网上面找能找到,有中文版--> <settings> <!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 打印运行时的日志--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!-- 配置类型别名,可以通过别名的方式来减少mapper.xml中的paramerType和resuletType的复杂度--> <typeAliases> <typeAlias alias="business" type="com.li.mybatis.entity.Business"/> <typeAlias alias="produce" type="com.li.mybatis.entity.Produce"/> <!-- 也可以直接配置包名,这里的别名就是entity包下的类名小写--> <!-- <package name="com.li.mybatis.entity"/>--> </typeAliases> <!-- 配置JDBC连接数据库--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/liwithmybatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- Mybatis的单表配置,与要查询的表相关联--> <mappers> <mapper resource="mappers/business-mapper.xml"/> </mappers> </configuration>
-
单表配置:要查询哪张表就配哪张表,一般在resources文件夹下再建一个mappers文件夹用来保存单表的配置:
<?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可以随便写--> <mapper namespace="li"> <!-- 字段转换,将实体类的属性与数据库的字段相匹配--> <!-- 增加--> <insert id="insert"> insert into business values(default,#{businessName},#{businessPassword}) </insert> <!-- 删除--> <delete id="del"> delete from business where business_id=#{businessId} </delete> <!-- 修改--> <update id="update"> update business set business_name=#{businessName} where business_id=#{businessId} </update> <!-- 查询--> <select id="select" resultType="business"> select * from business where business_id=#{businessId} </select> </mapper>
-
测试类:增删改都需要提交(commit)才会生效
public class App { public static void main( String[] args ) throws IOException { // 启动mybatis框架 // 读取配置文件,这里注意Resources的包被导错了,是Apache的包 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); o // 构建SQLSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 创建SqlSession SqlSession sqlSession = factory.openSession(); // 开始执行方法,注意要提交,不然没有效果,方法的参数("namespace.id",mapper中sql的参数),传实体类注意sql的参数要与实体类的属性相对应 // Business business = new Business(); // business.setBussinessName("毛毛虫"); // business.setBussinessPassword("aichicao"); // sqlSession.insert("li.insert",business); // sqlSession.commit(); // 删除 // sqlSession.delete("li.del",4); // sqlSession.commit(); // 修改 // Business business = new Business(); // business.setBusinessId(1); // business.setBusinessName("吴彦祖"); // sqlSession.update("li.update",business); // sqlSession.commit(); // 查询 Business business = sqlSession.selectOne("li.select", 1); System.out.println(business); } }
-
-
Mybatis的Mapper接口方式进行查询
-
dao层的mapper接口:
public interface ProduceMapper { List<Produce> findProduce(); }
-
子表配置:
<?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"> <!--注意,这里的namespace要写接口路径--> <mapper namespace="com.li.mybatis.dao.ProduceMapper"> <select id="findProduce" resultType="produce"> select * from produce </select> </mapper>
-
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> <!-- Mybatis的setting配置,这些去官网上面找能找到,有中文版--> <settings> <!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 打印运行时的日志--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!-- 配置类型别名,可以通过别名的方式来减少mapper.xml中的paramerType和resuletType的复杂度--> <typeAliases> <typeAlias alias="business" type="com.li.mybatis.entity.Business"/> <typeAlias alias="produce" type="com.li.mybatis.entity.Produce"/> <!-- 也可以直接配置包名,这里的别名就是entity包下的类名小写--> <!-- <package name="com.li.mybatis.entity"/>--> </typeAliases> <!-- 配置JDBC连接数据库--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/liwithmybatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- Mybatis的单表配置,与要查询的表相关联--> <mappers> <mapper resource="mappers/business-mapper.xml"/> <mapper resource="mappers/produce-mapper.xml"/> </mappers> </configuration>
-
测试类:
public class MapperApp { public static void main(String[] args) throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = factory.openSession(); // 生成一个实现类,实现类通过SQLSession访问xml中特定的语句(全类名.方法) // 全类名 <=> namespace // 方法名 <=> sql的id // 这里produceMapper就是Mybatis根据传入的接口生成一个实现类(代理) ProduceMapper produceMapper = sqlSession.getMapper(ProduceMapper.class); List<Produce> produces = produceMapper.findProduce(); System.out.println(produces); } }
-
-
Mybatis的多表查询:
-
在实际的应用中,肯定多表连接查询的场景要比单表连接查询多得多,下面,就来展示多表查询
-
mapper接口:
public interface ProduceMapper { List<Produce> findProduce(); // 这里使用@Param(参数名)注解与mapper.xml中的#{参数名},参数名要对应,结果用map来接收 List<Map<String,Object>> finProduceAndByBusiness(@Param("businessId") Integer businessId, @Param("produceId")Integer produceId); }
-
子表.xml:
<!--注意,这里的namespace要写接口路径--> <mapper namespace="com.li.mybatis.dao.ProduceMapper"> <select id="findProduce" resultType="produce"> select * from produce </select> <select id="finProduceAndByBusiness" resultType="map"> select b.business_name,p.produce_name from business b,produce p where b.business_id=#{businessId} and p.produce_id=#{produceId} </select> </mapper>
-
直接写结果吧,测试代码就跟上面的一样了,注意,这里打印出来的map中key是数据库的字段
控制台会打印出: [{business_name=吴彦祖, produce_name=百事可乐}]
-
也可以使用另外的方式,就是重新来一个实体类,这个实体类有所有要连接的表的字段
-
-
Mybatis的动态SQL
-
mybati的一大重要特征就是生成动态sql,什么是动态sql呢,举个例子:有一个user表,里面有user_id,user_name,user_password,当我们需要对其中的一条数据进行修改的时候,可能会出现修改这条信息的一个字段,也可能是多个字段,那么如果没有动态sql,我们将需要写多条sql来完成不一样的修改,有了动态sql我们就只需要写一条就可以了
-
user类:
@Data public class User { private int userId; private String userName; private String userPassWord; }
-
Mapper接口:
public interface UserMapper { User userQuery(User user); }
-
子表.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.li.mybatis.dao.UserMapper"> <!-- 查询--> <select id="userQuery" resultType="user"> select * from user <where> <if test="userId!=null"> and user_id=#{userId} </if> <if test="userName!=null"> and user_name=#{userName} </if> </where> </select> </mapper>
-
测试类:
public class MapperApp { public static void main(String[] args) throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = factory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //这里只传入了userId User user = new User(); user.setUserId(1); User user1 = userMapper.userQuery(user); System.out.println(user); } }
-
这里我们直接看一下生成的sql
select * from user WHERE user_id=?
-
将userName也传入后生成的sql
select * from user WHERE user_id=? and user_name=?
-
在更新的时候也一样,将
where
换成set
就ok<update id="upd" parameterType="user"> update user <set> <if test="userName!=null"> ,user_name=#{userName} </if> <if test="userPassword!=null"> ,user_password=#{userPassword} </if> </set> where user_id=#{userId} </update>
-
choose
,when
,otherwise
,这里的相当于if , else if, else这种关系<select id="cwoFind" parameterType="pro" resultType="pro"> select * from produce <where> <choose> <when test="pId!=0"> and p_id=#{pId} </when> <when test="pName!=null"> and p_name=#{pName} </when> <otherwise> p_number=111 </otherwise> </choose> </where> </select>
-
foreach
,当我们需要对一个集合进行遍历,也就是需要使用in(参数1,参数2…)的时候<select id="forFind" parameterType="list" resultType="map"> select * from produce where p_id in <foreach collection="list" open="(" close=")" separator="," item="n"> #{n} </foreach> </select> 这里的几个参数的意思: collection 被迭代的列表 open 迭代之前加入 close 迭代之后加入 separator 迭代时的分隔符 item 迭代的变量名
-
foreach测试类代码:
// foreach的测试 List<Integer> list=new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); List<Produce> produces = userDao.forFind(list); System.out.println(produces);
-
生成的sql:
select * from produce where p_id in ( ? , ? , ? , ? , ? )
-
trim
,trim可以理解为等同于where<select id="userQuery" resultType="user"> select * from user <trim prefix="where" prefixOverrides="and" > <if test="userId!=null"> and user_id=#{userId} </if> <if test="userName!=null"> and user_name=#{userName} </if> </trim> </select> prefix trim标签内sql语句加前缀 suffix 。。。。。。。。加后缀 prefixOverrides 。。。去除多余的前缀 suffixOverrides 。。。。去除多余的后缀
-
-
Mybatis的缓存:
-
一级缓存:
- mybatis的一级缓存指的是Session缓存,一级缓存的作用域默认是一个SqlSession.mybatis默认开启的也是一级缓存.
- 也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并将数据写入到缓存中,第二次以后执行相同的sql时就是直接去缓存中取.
- 一级缓存,事务提交后缓存会清空
- 配置,在setting里面:
\<setting name="localCacheScope" value="{SESSION | STATEMENT}"/>
- 注意:当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中,每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。
如有需要可以启用二级缓存。
-
我们执行如下测试代码:
public class App { public static void main( String[] args ) throws IOException { // 读取配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 构建SQLSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 创建SqlSession SqlSession sqlSession = factory.openSession(); // 查询 Business business = sqlSession.selectOne("li.select", 1); // sqlSession.commit(); Business business1 = sqlSession.selectOne("li.select", 1); System.out.println(business); System.out.println(business1); } }
-
看一下控制台打印的日志:这里就没有再另外生成一个sql再去进行数据库查询了
==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai) Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai)
-
当我们将SQLSession提交(commit),再来看一下控制台:这里就又产生了一个sql说明进行了一次数据库查询,说明缓存被清空了
==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 ==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai) Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai)
-
二级缓存:
-
-
mybatis的二级缓存是指mapper映射文件.二级缓存的作用域是同一个namespace的mapper映射文件内容,多个SqlSession共享.Mybatis需要手动设置启动二级缓存.
-
二级缓存是默认启用的(要生效需要对每个Mapper进行配置),如果想取消,则可以通过Mybatis配置文件中的元素下的子元素来指定cacheEnabled为false.
-
二级缓存在事务提交之后才可以共享,跨SQLSession
-
配置1:true为开启,默认为开启状态:
<setting name="cacheEnabled" value="true"/>
-
配置2:在需要开启二级缓存的子mapper中添加:
<!-- 开启二级缓存--> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> eviction:清除策略,LRU – 最近最少使用:移除最长时间不被使用的对象。 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。 WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 flushInterval:(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。 size:(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。 readOnly:(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
-
-
测试代码:
public class App { public static void main( String[] args ) throws IOException { // 读取配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml"); // 构建SQLSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 创建SqlSession SqlSession sqlSession = factory.openSession(); // 查询 Business business = sqlSession.selectOne("li.select", 1); SqlSession sqlSession1 = factory.openSession(); Business business1 = sqlSession1.selectOne("li.select", 1); System.out.println(business); System.out.println(business1); } }
-
这时候因为没有提交,所以二级缓存并没有生效,控制台会打印出:就有2遍sql
==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 Cache Hit Ratio [li]: 0.0 Opening JDBC Connection Created connection 2009221452. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] ==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai) Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai)
-
在执行完第一个sqlsession之后,我们将其提交:
// 创建SqlSession SqlSession sqlSession = factory.openSession(); // 查询 Business business = sqlSession.selectOne("li.select", 1); SqlSession sqlSession1 = factory.openSession(); Business business1 = sqlSession1.selectOne("li.select", 1); System.out.println(business); System.out.println(business1);
-
控制台会打印出:可以这里只出现了一次sql,另一次是从cache里面查询的
==> Preparing: select * from business where business_id=? ==> Parameters: 1(Integer) <== Columns: business_id, business_name, business_password <== Row: 1, 吴彦祖, wohenshuai <== Total: 1 Cache Hit Ratio [li]: 0.5 Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai) Business(businessId=1, businessName=吴彦祖, businessPassword=wohenshuai)
-
还可以在一个mapper中引用另一个mapper的缓存配置:
<!--引用另一个namespace的缓存配置--> <cache-ref namespace="com.woniuxy.mybatis.UserDAO"/>
-
-
二级缓存的使用原则
- 只能在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。 - 在单表上使用二级缓存
如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。 - 查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
- 只能在一个命名空间下使用二级缓存