mybatis
mybatis概述、环境搭建、入门案例、自定义mybatis框架
mybatis概述
mybatis是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql语句本身, 而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis通过xml 或注解的方式将要执行的各种statement配置起来,并通过java对象和statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并 返回。
采用 ORM (Object Relational Mappging 对象关系映射 把数据库表和实体类及实体类的属性对应起来,可以操作实体类就实现操作数据表)思想解决了实体和数据库映射的问题,对 jdbc进行了封装,屏蔽了 jdbc api 底层访问细节,使我 们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
入门案例
- 创建maven工程并导入坐标
- 创建实体类和dao接口
- 创建Mybatis的主配置文件
SqlMapConfig.xml - 创建映射配置文件
IUserDao.xml
注意事项:- 创建IUserDao.xml和IUserDao.java ,名称是为了和之前保持一直。
在Mybatis中,他把持久层的操作接口名称和映射文件也叫做Mapper
所以 IUserDao 和 IUserMapper 是一样的 - 在 idea中创建目录时,和包不同
包:com.lx.dao 是三级结构
目录:com.lx.dao 是一级结构 - Mybatis的映射文件位置必须和dao 接口的包结构相同
- 映射配置文件的 mapper 标签 namespace 属性的取值必须是 dao 接口的全限定类名
- 映射配置文件的操作配置(select),id 属性的取值必须是 dao 接口的方法名
当我们遵从了3、4、5点之后,在开发中就无需再写 dao 类
- 创建IUserDao.xml和IUserDao.java ,名称是为了和之前保持一直。
mybatis的入门案例
-
读取配置文件
-
创建SqlSessionFactory工厂
-
创建SqlSession
-
创建Dao接口的代理对象
-
执行dao中的方法
-
释放资源
注意事项:
在映射配置中告知mybatis要封装到哪个实体类中
配置方式:指定实体类的全限定类名123456
mybatis基于注解的入门案例:
把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并指定SQL语句
同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名
注意:
在实际开发中,都是越简便越好,所以都采用不写dao实现类的方式,不管使用XML还是注解配置
但是Mybatis支持写dao的方式
//1.读取配置文件
InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(in);
//3.使用工厂生产 SqlSession 对象
SqlSession session=factory.openSession();
//4.使用sqlSession创建 dao接口的代理对象
IUserDao userDao=session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users=userDao.findAll();
for (User user:users){
System.out.println(user.toString());
}
//6.释放资源
session.close();
in.close();
自定义mybatis
比如需要实现查询所有:创建代理对象,实现查询所有
- 查询所有实现在工具类中Executor.java
- 调用工具类实现,在创建dao实现类增强时调用
- 创建代理对象DefaulSqlSession.java时创建dao实现类
- 调用,在DefaulSqlSession中得MapperProxy调用
- MapperProxy.java,从某处找出要执行的语句和封装的结果。从配置文件找。怎么读取?通过工具类XMLConfigBuilder来读取。读取后需要存,存在Configuration对象中
配置文件配置jdbc
<configuration>
<properties resource="jdbcConfig.properties">
<!-- 配置properties,可以在标签内部配置连接数据库的信息,也可以通过属性引用外部配置文件的信息
resources属性:指定配置文件的位置,按照类路径的写法来写,并且必须存在于类路径下。
-->
<!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123"/>-->
</properties>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
jdbcConfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=123
mabatis基本使用、单表CRUD操作、参数和返回值、dao的编写、配置细节(标签的使用)
mybatis自定义和环境搭建+完善自定义Mybatis的注解开发
- 有一个SqlSessionFactoryBuilder来接收SqlMapConfig.xml文件流,构建出SqlSessionFactory对象(SqlSessionFactory本身不能操作数据库,借助生产的SqlSession来进行操作)
- SqlSessionFactory读取SqlMapConfig.xml中的连接数据库和mapper映射信息。用来生产出真正操作数据库的SqlSession对象
- SqlSession对象两大功能:生产接口代理对象,定义通用增删改查方法。除了数据库连接信息,还需要得到sql语句
- (生产接口代理对象)首先创建一个dao接口的代理实现类,代理实现类中调用增删改查方法来实现功能
- (定义通用增删改查方法)写dao实现类,直接调用增删改查的方法
- 封装结果集
简单的CRUD操作
IUserDao.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.lx.dao.IUserDao">
<select id="findAll" resultType="com.lx.domain.User">
select * from user;
</select>
<insert id="saveUser" parameterType="com.lx.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user (username,address,sex,birthday)values (#{username},#{address},#{sex},#{birthday});
</insert>
<update id="updateUser" parameterType="com.lx.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id =#{id};
</update>
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{id}
</delete>
<select id="findById" parameterType="Integer" resultType="com.lx.domain.User">
select * from user where id=#{id}
</select>
<select id="findByName" parameterType="String" resultType="com.lx.domain.User">
<!-- select * from user where like #{String} Statement对象的字符串拼接sql-->
select * from user where like '%${value}%'<!-- PrepatedStatement的参数占位符 -->
</select>
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
IUserDao.java
package com.lx.dao;
import com.lx.domain.User;
import java.util.List;
public interface IUserDao {
public List<User> findAll();
void saveUser(User user);
void updateUser(User user);
void deleteUser(Integer id);
User findById(Integer id);
List<User> findByName(String username);
int findTotal();
}
SqlMapConfig.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>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/lx/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
test类
package com.lx.test;
import com.lx.dao.IUserDao;
import com.lx.domain.User;
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.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
public class MybatisTest {
private InputStream in;
private SqlSession session;
private IUserDao userDao;
@Before
public void init() throws Exception{
in= Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
session=factory.openSession();
userDao=session.getMapper(IUserDao.class);
}
@After
public void destroy()throws Exception{
in.close();
session.close();
}
@Test
public void testFindAll() {
List<User> users= userDao.findAll();
for (User user:users){
System.out.println(user);
}
}
@Test
public void testSave() {
User user=new User();
user.setUsername("mybatis saveuser");
user.setAddress("mybatis");
user.setSex("n");
user.setBirthday(new Date());
userDao.saveUser(user);
//提交事务
session.commit();
}
@Test
public void testUpdate(){
User user=new User();
user.setUsername("mybatis saveuser");
user.setAddress("mybatis");
user.setSex("n");
user.setId(5);
user.setBirthday(new Date());
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(6);
}
@Test
public void testFindOne(){
userDao.findById(7);
}
@Test
public void testFindByName(){
//select * from user where like #{String}
//List<User> users=userDao.findByName("%王%");
//select * from user where like '%${value}%'
List<User> users=userDao.findByName("L");
for (User user:users){
System.out.println(user);
}
}
@Test
public void testFindTotal(){
int count=userDao.findTotal();
System.out.println(count);
}
}
数据字段问题:
mysql数据库在win系统下不区分大小写,Linux严格区分大小写
在win下 userName = username,userId != id
如何解决?
想办法进行匹配
select * from user;
从sql层面来解决
select id as userId,username as userName,address as userAddress,sex as userSex,
brithday as userBrithday from user
也可以采用配置的方式
<!-- 配置 查询结果的列名和实体类的属性名对应关系 -->
<resultMap id="userMap" type="com.lx.domain.user">
<!--主键字段对应-->
<id property="userId" column="id"></id>
<!--非主键字段对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
<!--使用结果类型定义时-->
//<!--<select id="findAll" resultType="com.lx.domain.User">-->
<select id="findAll" resultMap="userMap">
select * from user;
</select>
<!--执行效率慢了,开发效率提高-->
mybatis的连接池、事务控制,深入和多表,多表查询(一对多,一对一,多对多)
连接池
连接池:
- 可以减少连接的次数,连接池就是一个存储连接的一个容器。其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一个连接,该集合还必须保证队列的特性:先进先出。
mybatis的连接池:
- mybatis连接词提供了三种方式的配置:
- 配置的位置:
- 主配置文件sqlMapConfig.xml中的dataSource标签,type属性表示采用何种连接池方式。
- type属性的取值:
- POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
- UNPOOLED 采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource的接口,但是并没有实现池的思想。
- JNDI 采用服务器提供的JNDI技术,来获取DataSource对象,不同的服务器所能拿到的DataSource不一样的。如果不是web或者maven的war工程,是不能使用的。tomcat中采用的连接池就是dbcp连接池
- 配置的位置:
从池中获取一个连接,用完归还
事务控制
Mybatis中的事务:
- 什么是事务
- 事务的四大特性ACID
- 原子性
- 一致性
- 隔离性
- 持久性,具体不解释了,数据库原理的内容。
- 不考虑隔离性会产生的三个问题
- 脏读
- 不可重复读
- 虚读
- 四种隔离级别
- 读取未提交内容
- 读取提交内容
- 可重读
- 可串行化
通过sqlSession对象的commit和rollback方法实现事物的提交和回滚
动态sql语句
直接附上代码
<!--IUserDao.xml-->
</resultMap>
<!-- 抽取重复的sql语句-->
<sql id="defaultUser">
select * from user
</sql>
<select id="findAll" resultMap="userMap">
<include refid="defaultUser">
</include>
</select>
<select id="findById" parameterType="Integer" resultMap="userMap">
select * from user where id=#{id}
</select>
<select id="findByName" parameterType="String" resultMap="userMap">
<!-- select * from user where like #{String} Statement对象的字符串拼接sql-->
select * from user where like '%${value}%'<!-- PrepatedStatement的参数占位符 -->
</select>
<!-- 根据queryVo的条件查询用户-->
<select id="findByVo" parameterType="com.lx.domain.QueryVo" resultMap="userMap">
<!-- select * from user where like #{String} Statement对象的字符串拼接sql-->
select * from user where like #{user.username}<!-- PrepatedStatement的参数占位符 -->
</select>
<!-- <select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user where 1 = 1
<if test="username != null">
and username=#{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</select>-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="username != null">
and username=#{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</where>
</select>
<!-- 根据queryVo中的 id集合 实现查询用户列表-->
<select id="findUserByIds" resultMap="userMap" parameterType="queryvo">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open="and id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
测试类
//MybatisTest
@Test
public void testFindByCondition(){
User user=new User();
user.setUsername("江安河");
user.setSex("男");
List<User> users=userDao.findUserByCondition(user);
for (User userr:users){
System.out.println(userr);
}
}
/**
* 测试foreach标签的使用
*/
@Test
public void testFindInIds(){
QueryVo vo=new QueryVo();
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
vo.setIds(list);
List<User> users=userDao.findUserByIds(vo);
for (User userr:users){
System.out.println(userr);
}
}
User类、
package com.lx.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
QueryVo
package com.lx.domain;
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
多表查询
为什么需要多表查询?
- 比如一个用户有多个订单,一个订单可能有多个商品,一对多
- 多个订单是一个用户下的,今天十个订单,其中三个是同一个人下的,显示就是3-1,1-1,1-1……,多对一
实现如下:
一张USER表,内容为:TEL_NUMBER,USERNAME,SEX,ID之类的
也就是一个手机号,这个人交啥,什么性别,分配了个ID。
需要一个中间表,比如一张表,内容为:TEL_NUMBER,QQ
如何理解呢?手机号唯一,缺点了同一个人,也可以是身份证号之类的唯一标识。这个人有多个QQ号(也可以是游戏角色之类的)。
还需要一张QQ号表,内容为:QQ,SEVER,LEVEL。
如何理解呢?一个QQ号,在一个游戏区里,有一个等级为?的角色
如果要查询一个人他所有的游戏角色,就只需要调用多表查询,由TEL_NUMBER来通过中间表查出他的所有游戏角色表。
为什么不把所有数据写道一张表里呢?会导致一张表非常的笨重臃肿,
代码实现如下
SELECT * FROM QQ号表 qq
LEFT OUTER JOIN 中间表 uq ON qq.TEL_NUMBER = uq.TEL_NUMBER
LEFT OUTER JOIN 用户表 u ON u.id=uq.uid
输出基本靠吼
延迟加载
延迟加载:
- 在真正使用数据时才发起查询,不用的时候不查询,按需加载、懒加载。
对应的,也有立即加载。
比如一个憨憨,创建了一千个游戏角色,他一查QQ,你要不要把一千个游戏角色给他看?还是,他要看的时候再给他看。
好处:节约资源
需要在SqlMapConfig.xml配置,配置在中
<!-- 配置参数 -->
<settings>
<!--配置开启全局延迟加载的开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载-->
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
QQ号Dao.xml也需要进行相关配置
<association property="" column="" javaType="" select="第二次调用查询的方法的全限定类名+“.”+方法名">
</association>
对原有的select语句进行拆分,以前是外连接,现在是查询两次
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM USER u LEFT OUTER JOIN account a ON u.id=a.uid
</select>
<select id="findById" parameterType="Integer" resultType="user">
select * from user where id=#{id}
</select>
效果如下、
- 需要查询所有数据的时候
- 不需要查询所有数据的时候
可以看到,当把Account输入信息给注释的时候,并不会发生第二次查询
缓存
Mybatis中有一级缓存和二级缓存,缓存就是存在内存里的东西,拿的快,占内存资源。 目的是减少和数据库的交互次数。理论上内存够大的话,是可以把整个数据库缓存进去的。
有的数据不适用于缓存,比如经常改变却很重要的。比如股票价格。
一级缓存:
- 对 SqlSession 对象的缓存。
- 执行查询之后,查询结果会同时存入到SqlSession提供的一块区域中该区域是一个Map,当我们再次查询同样的数据,mybats会先去sqlsession中查询是否有,有的话直接用
- 当SqlSession对象消失时,mybatis的一级缓存也消失
- 当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存
二级缓存:
- 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创
建的SqlSession共享其缓存。 - 二级缓存的使用步骤:
- 第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
- 第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
- 第三步:让当前的操作支持二级缓存(在select标签中配置)
注解的方式开发:
代码一看便知
package com.lx.dao;
import com.lx.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
/**
* mybatis中,CRUD有四个注解
* @Select @Insert @Update @Delete
*/
@CacheNamespace(blocking = true)
public interface IUserDao {
@Select(value = "select * from user")
@Results(id = "userMap",value = {
@Result(id = true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "birthday",property = "userBirthday"),
@Result(column = "sex",property = "userSex"),
@Result(column = "id",property = "accounts",
many = @Many(select = "com.lx.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
@Select("select * from user where id=#{id}")
@ResultMap(value = {"userMap"})
User findById(Integer userId);
//@Select("select * from user where username like #{username}")
@Select("select * from user where username like '%${value}%' ")
@ResultMap("userMap")
List<User> findUserByName(String name);
}
方法的测试和以前相同
io流读取配置文件——SqlSessionFactory——工厂生产SqlSession——session获得dao——dao调用方法
给位,一起冲鸭