MyBatis持久层框架
JDBC操作中会有大量的费管代码—工具类
映射问题
获取连接用时占操作数据库表时间的一半以上的时间。
-
Connection属于线程不安全的对象
-
享元模式—连接池
虽然没有办法减少单次使用成本,但是可以明显降低平均使用成本
引入MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象) 为数据库中的记录。
ORM对象关系映射,解决编程中的对象模型和关系模型的阻抗不匹配性,达到以操作对象的方式操作关系型数据库
中文参考网站: MyBatis中文网
1、添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
2、创建表
create table tb_users(
id bigint primary key auto_increment,
username varchar(32) not null unique,
password varchar(32) not null,
birth datetime default now(),
sex boolean default 1
)engine=innodb default charset utf8;
插入测试数据
insert into tb_users(username,password) values('yanjun','123456');
insert into tb_users(username,password) values('tanjie','333333');
insert into tb_users(username,password) values('xiaozhe','66666');
3、添加MyBatis的核心配置文件
实际上针对核心配置文件的名称和位置没有强制要求,但是一般位于resources目录下,名称为mybatis-config.xml
核心配置文件中主要负责:
- 配置数据源。
- 配置运行时参数
- 注册映射元文件
<?xml version="1.0" encoding="UTF-8" ?><!-- 用于声明当前xml文件所支持的版本号和对应的编码字符集 -->
<!-- 声明当前xml文件所遵循的语法标准和对应的标签定义 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 核心配置文件的根标签 -->
<configuration>
<!-- 应用环境配置,可以针对不同的阶段定义不同的配置,多个配置使用id进行区分,默认使用哪个配置通过default进行指定 -->
<environments default="yan">
<!-- 某个具体的配置,这里使用id进行区分。允许定义多个,不同配置使用不同的id -->
<environment id="yan">
<!-- 针对不同的运行环境需要配置2方面的内容,分别是事务管理器和连接池 -->
<!-- transactionManager用于配置事务管理器,这里可以定义的type有2种:如果使用的是本地事务type=JDBC,
如果使用的是分布式事务则type=MANAGED 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整
个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将
closeConnection 属性设置为 false 来阻止默认的关闭行为 -->
<transactionManager type="JDBC"/>
<!-- 用于配置数据源。type有3种可选值
UNPOOLED采用直连方式连接数据库,每次使用时临时获取连接,使用完成则关闭连接;
POOLED采用MyBatis框架提供的一个简单连接池实现,实际开发中一般会替换为成品连接池,例如阿里的Druid;
JNDI使用容器管理的连接池,例如使用tomcat提供的连接池
-->
<dataSource type="POOLED">
<!-- 使用pooled连接池需要配置构建连接所需要的参数,例如驱动串、连接串、用户名称和口令,另外还有连接池相关配置,例如
最大连接数、最大空闲连接数等 -->
<!-- 定义配置的格式为 name为配置参数的名称,value为对应的配置参数值 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///test3?serverTimezone=UTC"/>
<property name="username" value="root" />
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
4、参照数据表定义对应的实体类,一般建议列名称和属性名称一致,可以考虑使用驼峰记法
@Data
public class User implements Serializable {
private Long id;
private String username;
private String password;
private Date birth;
private Boolean sex;
}
5、定义映射元文件
事实上也没有什么强制规则,只是一般建议位于resources/mapper目录下,和类名称相关的方式进行命名。例如实体类名称为User,则映射元文件名称为UserMapper.xml
- 映射关系,也就是类和表之间的对应关系、属性和列之间的对应关系,以及列或者属性的数据类型
- 属性名称和列名称可以不一致,如果一致则可以使用框架提供的自动映射,以减少配置;如果不一致则必须进行配置
- 定义执行增删改查所指定的sql语句
- mybatis是一种半自动化ORM框架,以编写sql语句的工作量为代价换取高灵活性
- hibernate是一种自动化ORM框架,无需编写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,用于通过引入名空间以保证当前配置文件中的所有命名的唯
一性。namespace一般和映射接口名称一致 -->
<mapper namespace="com.yan.dao.UserMapper">
<!-- resultMap用于声明实体类和表之间的对应关系autoMapping="true"自动映射,
要求实体类中的属性名称和列名称一致,自动支持驼峰计法。id用于区分多个不同的resultMap
配置,type用于指定对应的实体类 -->
<resultMap type="com.yan.entity.User" id="baseMapper">
<!-- id用于配置主键和标识属性之间的对应关系,column为列名称,property为对应的属性名称,
jdbcType用于指定对应的类型,对应的类型名称来自于java.sql.Types中定义的常量名称 -->
<id column="id" jdbcType="BIGINT" property="id"/>
<!-- result用于配置非键列和属性之间的关系,其中属性和id含义一致 -->
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="birth" property="birth" jdbcType="DATE"/>
<result column="sex" property="sex" jdbcType="BOOLEAN"/>
</resultMap>
<!-- 查询使用select标签,插入insert标签,修改update标签,删除delete标签 -->
<!-- 查询操作对应的sql语句配置,parameterType用于指定执行操作时的参数类型,
resultMap用于指定获取结果时所使用的resultMap映射,从而达到将一行数据转换为一个值bean对象的目的 -->
<select id="selectAll" resultMap="baseMapper">
select * from tb_users
</select>
</mapper>
定义映射元文件后必须到核心配置文件中进行注册,修改mybatis-config.xml添加配置
<!-- 用于注册映射元文件,可以注册多个 -->
<mappers>
<!-- 一个mapper标签注册一个映射元文件,也可以使用包扫描减少配置个数 -->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
6、通过MyBatis框架提供的API调用前面的相关配置完成数据库的查询操作
public class Test2 {
public static void main(String[] args) throws Exception {
// 构建用于读取核心配置文件的输入流,实际上使用getResourceAsReader和getResourceAsStream是等价的
// Resources.getResourceAsStream(null)
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
// 1、创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
// 2、通过SqlSessionFactory对象创建SqlSession对象,可以将SqlSession对象看作一个持久化管理器,通过它完成CRUD操作的调用
SqlSession session = factory.openSession();
// 3、通过SqlSession对象调用在映射元文件中配置的select操作
// 参数1是对应的操作配置名称,就是映射元文件中的namespace+操作id两部分,参数2
List<User> userList = session.selectList("com.yan.dao.UserMapper.selectAll");
// 常规的查询操作有2个方法,一个是只返回一个值的.selectOne,一个是返回多个值的selectList
userList.forEach(System.out::println); //使用lambda表达式输出显示查询结果
//4、 关闭
session.close();
// 一般针对factory不执行关系操作
}
}
7、添加一个用户信息
7.1、修改映射元文件UserMapper.xml,添加对应的insert
<insert id="insert" parameterType="com.yan.entity.User">
insert into tb_users(
<!-- 因为有些属性不是必须有值,所以这里进行判断,如果传入参数对应的属性不为空,则表示当前属性对应的列需要插入数据 -->
username,password <!-- 数据库表中不允许username和password -->
<!-- if标签用于实现判断,test中为一个boolean类型表达式,如果值为tru,则标签体会被执行 -->
<if test="id!=null">,id</if>
<if test="birth!=null">,birth</if>
<if test="sex!=null">
,sex
</if>
) values(
#{username} <!-- #{}用于获取参数类型中的指定列的对应值 -->
,#{password}
<if test="id!=null">
,#{id}
</if>
<if test="birth!=null">
,#{birth,jdbcType=DATE}
</if>
<if test="sex!=null">
#{sex}
</if>
)
</insert>
编程调用
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
User user=new User();
user.setUsername("小哲");
user.setPassword("888888");
session.insert("com.yan.dao.UserMapper.insert",user);
session.commit();
session.close();
8、删除数据
在具体的项目开发中一般不使用物理删除,而是使用逻辑删除
-
物理删除实际上就是删除表中的数据
-
逻辑删除实际上就是给需要删除的数据添加了一个删除标记,而不是真正的删除
给用户表添加逻辑删除支持
create table tb_users( id bigint primary key auto_increment, ... deleted boolean default 0 );
引入额外的列deleted用于表示本行数据已经被删除,所谓的删除的操作就是将当前行数据deleted赋值为1
修改映射元文件添加delete操作
<!-- 按照主键执行删除操作,参数类型为主键类型,增删改返回一个int类型数据,用于表示受影响行数 -->
<delete id="deleteById" parameterType="long">
delete from tb_users where id=#{id}
</delete>
编码调用
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
int len=session.delete("com.yan.dao.UserMapper.deleteById",4L);
if(len>0)
System.out.println("删除成功");
else
System.out.println("删除失败!");
session.commit();
session.close();
9、按照主键加载对应的数据
<select id="selectById" parameterType="long" resultMap="baseMapper">
select * from tb_users where id=#{id}
</select>
编程调用
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
User user=session.selectOne("com.yan.dao.UserMapper.selectById",2L);
session.close();
System.out.println(user);
10、修改数据
按照主键修改其它的非空属性
<update id="updateById" parameterType="com.yan.entity.User">
update tb_users set id=#{id}
<!-- test后面写的是属性名,就是user对象中的属性,两个尖括号之间的是列名,#{}之间的是属性名 -->
<if test="username!=null">
,username=#{username}
</if>
<if test="password!=null">
,password=#{password}
</if>
<if test="birth!=null">
,birth=#{birth,jdbcType=DATE}
</if>
<if test="sex!=null">
,sex=#{sex,jdbcType=BOOLEAN}
</if>
where id=#{id}
</update>
编程调用
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
User user=session.selectOne("com.yan.dao.UserMapper.selectById",2L);
user.setBirth(new Date(1989-1900,2-1,3));
int updated=session.update("com.yan.dao.UserMapper.updateById",user);
if(updated>0)
System.out.println("修改成功!"+user);
else
System.out.println("修改失败!");
session.commit();
session.close();
System.out.println(user);
SqlSessionFactory是MyBatis的关键字,它是单个数据库映射关系经过编译后的内存镜像,SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来获得,而SqlSessionFactoryBuildr则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例
每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。SqlSessionFactory是线程安全的,他一旦被创建,应该在应用执行期间都存在,在应用运行期间不要重复创建多次,建议使用单例模式,SqlSessionFactory是创建SqlSession的工厂。
11、按条件查询
按照传入的值bean的非空属性值进行等值查询,多个属性之间是条件与
<select id="selectByExample" parameterType="com.yan.entity.User" resultMap="baseMapper">
select * from tb_users where 1=1
<if test="id!=null">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
<if test="password!=null">
and password=#{password}
</if>
<if test="birth!=null">
and birth#{birth,jdbcType=DATE}
</if>
<if test="sex!=null">
and sex=#{sex,jdbcType=BOOLEAN}
</if>
</select>
编码调用
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
User user=new User();
user.setId(3L);
List<User> userList=session.selectList("com.yan.dao.UserMapper.selectByExample",user);
session.commit();
session.close();
userList.forEach(System.out::println);
问题:
- 费管代码问题–工具类、继承、模板模式…
- 必须写但跟程序功能无关
- 编程中引用sql语句依赖的是statementId,在具体的编程中使用的是字符串类型,则无法利用IDE工具的检查功能,
session.selectList("com.yan.dao.UserMapper.selectByExample",user)
– 引入接口编程
MyBatis中的接口编程
给映射元文件添加一个Mapper接口 ,凡是在接口中声明的方法同时定义在接口中,具体调用时不再使用字符串的方式进行调用,而是通过接口进行调用
映射元文件
<mapper namespace="com.yan.dao.UserMapper">
这里的namespace要求对应的时具体的Mapper接口的全名
package com.yan.dao;
public interface UserMapper {
}
后面添加CRUD对应的sql语句声明时,同时向接口中添加对应的方法
CRUD是指在做计算处理时的增加(Create)、读取(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写,CRUD主要被用在描述软件系统中
数据库或者持久层
的基本操作功能
<select id="selectById" parameterType="long" resultMap="baseMapper">
select * from tb_users where id=#{id}
</select>
同时在对应的Mapper接口中添加方法,要求id和方法名称一致,parameterType参数类型和resultMap结果类型必须和方法声明一致
- parameterType:接口中方法参数的类型, 类型的完全限定名或别名
如:parameterType = “java.lang.Integer” parameterType = “int”
这个属性是可选的,因为可以推断出具体传入语句的参数,默认值为未设置(unset)。接口中方法的参数从 java 代码传入到 mapper 文件的 sql 语句
- MyBatis的每一个查询映射的返回类型都是ResultMap,当我们提供返回类型属性是resultType时,MyBatis会自动给我们把对应值赋给resultType所指定对象的属性,当我们提供返回类型是resultMap时,将数据库中列数据复制到对象的相应属性上,可以用于复制查询,两者不能同时用
package com.yan.dao;
import com.yan.entity.User;
public interface UserMapper {
User selectById(Long id);
}
通过Mapper接口调用对应的selectById操作
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
SqlSession session = factory.openSession();
//接口对象是由MyBatis框架提供的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
User user=userMapper.selectById(2L);
session.commit();
session.close();
System.out.println(user);
工具类的定义
依据:编程中涉及到的主要接口有:1、。2、 SqlSession接口来执行命令,获取映射器实例和进行管理事务。3、 用于读取封装的SqlSessionFactoryBuilder。
1、SqlSessionFactoryBuilder可以配置信息并完成SqlSessionFactory创建,一旦创建factory完毕则没有必要长期保持,所以属于即用即丢型对象
2、SqlSessionFactory主要用于完成SqlSession创建工作的。属于重量级对象,因为其中包含了二级缓存;属于线程安全的对象。属于长生命周期对象。可以在编程中考虑使用单例模式【饿汉模式、懒汉模式–双检测大的懒汉模式】
public class MyBatisSessionFactory {
private MyBatisSessionFactory() {
}
private static SqlSessionFactory factory;
static {
try {
buildFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getFactory() {
return factory;
}
private static void buildFactory() throws Exception {
Reader r = Resources.getResourceAsReader("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(r);
}
}
3、SqlSession充当实体管理器的功能,提供了最基本的CRUD的方法;SqlSession是Connection对象的浅封装,所以要求使用try/finally结构,属于轻量级对象,线程不安全,所以考虑使用ThreadLocal管理SqlSession对象,或者将SqlSession对象当作即用即丢型对象进行使用
浅封装:session 对connection的一个封装,只调用连接,浅浅的封装一下没有完全封装
- Connection对象属于系统的稀有资源,因为一个数据库管理系统能够提供的连接数是有限的。要求针对Connection对象需要使用时进行创建,使用完毕必须保证及时关闭。try/finally结构
- 在web应用中一个请求的处理流程中,不管使用了多少个类,这些类都在同一个线程上运行
private static final ThreadLocal<SqlSession> ts = new ThreadLocal<>();
public static SqlSession getSession() throws Exception {
SqlSession session = ts.get();
if (session == null) {
if (factory == null)
buildFactory();
session = factory.openSession();
ts.set(session);
}
return session;
}
public static void closeSession() throws Exception {
SqlSession session = ts.get();
ts.set(null);
if(session!=null)
session.close();
}
4、Mapper接口对象是MyBatis针对特定接口提供的代理实现对象,实际上最终还是调用映射元文件中配置的sql语句。属于线程不安全的轻量级对象,可以按照即用即丢的方式进行使用【局部变量】
在编写mybatis的程序时,常见的做法时编写一个Mapper接口,再编写相应的映射文件,之后便可以初始化mybatis的环境,调用该接口的方法执行操作数据库的各中操作。Mapper接口是使用JDK代理生成的一个代理类。
Mapper组建
- Mapper文件和Mapper接口应该放在同一个接口中
- Mapper文件中的namespace应该设置为Mapper接口的全限定名称
- Mapper文件中的操作元素ID对应Mapper接口的方法名称
public static <T> T getMapper(Class<T> clz) throws Exception {
SqlSession session = getSession();
return session.getMapper(clz);
}
5、一般在持久层框架中默认都是采用事务回滚,如果不进行手动事务提交则修改操作会在执行结束时自动回滚撤销。在工具类中添加对应的事务管理方法
public static void commitTransaction() throws Exception {
SqlSession session = getSession();
if (session != null)
session.commit();
}
public static void rollbackTransaction() throws Exception{
SqlSession session = getSession();
if (session != null)
session.rollback();
}
6、引入过滤器针对session和事务进行统一管理。要求进入servlet之前打开session,退出执行之前提交、回滚事务,最后还需要保证关闭session对象
@WebFilter("*.do")
public class OpenSessionInViewFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
MyBatisSessionFactory.commitTransaction();
} catch (Exception e) {
try {
MyBatisSessionFactory.rollbackTransaction();
} catch (Exception e1) {
e1.printStackTrace();
}
throw new ServletException(e);
} finally {
try {
MyBatisSessionFactory.closeSession();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}