目录
1、简介
1.1、原始JDBC
在以前我们都是使用原始的JDBC,但是这种连接会造成创建、释放频繁造成系统资源的浪费从而影响性能,当sql变化时需要去改变Java代码,不符合开闭的原则,在查询时要手动将结果集封装到实体,插入时手动的提取实体到占位符,增加了代码量。
- 导入相关的 jar 包 或者依赖;
- 加载驱动,获取Connection
- 准备SQL语句,获取执行sql语句的Statement对象;
- 通过Statement对象执行SQL,返回结果到ResultSet对象;
- 使用ResultSet读取数据,然后通过代码转化为具体的对象;
- 释放资源
public List<User> selectAllUser(){
//原始的JDBC手动创建连接
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/库名?useUnicode=true&&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
String root = "root";
String password = "密码";
conn = DriverManager.getConnection(url,root,password);
ps = conn.prepareStatement("select * from `user`");
rs = ps.executeQuery();
List<User> list = new ArrayList<>();
while (rs.next()){
//获取数据
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
//添加到集合中
list.add(user);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
conn.close();
ps.close();
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.2、SpringJDBC
SpringJDBC是对原始JDBC做了封装,它将原始的JDBC的步骤做了封装,我们只需要完成相关配置,就可以直接连接数据库,在操作中只需要关注JdbcTemplate 对象即可。
<!--导入数据文件database.properties -->
<context:property-placeholder location="classpath:database.properties"/>
<!--配置德鲁伊数据库连接池 -->
<!-- 配置druid 连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--初始连接数-->
<property name="initialSize" value="${jdbc.initialSize}"/>
<!--最小空闲连接数-->
<property name="minIdle" value="${jdbc.minIdle}"/>
<!--最大活动连接数-->
<property name="maxActive" value="${jdbc.maxActive}"/>
<!--最大等待时间-->
<property name="maxWait" value="${jdbc.maxWait}"/>
</bean>
<!--jdbcTemplate连接数据库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void jdbcTest(){
String sql = "select * from `user`";
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
users.forEach(System.out::println);
}
1.3、MyBatis
原始的JDBC操作繁琐,每次执行SQL时每次都需要获取:Connection、PreparedStatement、ResultSet 这些数据库资源,然后就需要大量的try、catch、finally语句捕捉异常、关闭数据库资源。而SpringJDBC虽然对JDBC做了封装,大大的简化了操作流程,但是它的操作还是直接在业务层面的,这样会导致耦合性较强,且有sql注入的危险。所以就产生了第三方的框架,比如MyBatis。官网
MyBatis 是 apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code后,正式更名为MyBatis。代码于2013年11月迁移到Github。
它是一种半自动的对象关系映射(ORM)框架,它在Java对象和数据库的表之间建立映射关系(类对应表,属性对应字段),内部封装了JDBC相关操作,所以不用再去关注注册驱动、获取连接、执行 SQL、释放资源等操作,极大简化了持久层开发工作。其中SQL语句需要我们自己编写,虽然相对于自动的映射框架来说,增加了不少代码量,但是它有较高的灵活性,可以根据需要,自由地对SQL进行定制。
什么是ORM?即Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,就是数据库的表对应Java类,数据库字段对应Java属性,数据的一条数据对应Java一个对象。
全自动的ORM不需要编写SQL语句,只需要定义好映射关系,就可以直接进行CRUD操作了。而半自动的ORM,需要手动编写SQL语句。虽然这种半自动的ORM在操作上会麻烦很多,但是相对的它也更加灵活,可以根据需要,自由地对SQL进行定制。
MyBatis 是通过 XML 或注解配置Statement对象,并通过Java对象和 Statement中的SQL进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射成Java对象并返回。
在MyBatis 中有三个基本要素:
- 核心组件(核心的类和接口)
- MyBatis核心配置文件(mybatis-config.xml)
- SQL映射器(mapper.xml)
MyBatis还有一种增强的工具版本 Mybatis-Plus,它是在Mybatis的基础上做了一些增强,在不改变MyBatis的基础上,简化了开发。
Mybatis的使用过程:
-
添加mybatis的依赖
<!--mysql8依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!--mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- junit的依赖,测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.1</version> <scope>test</scope> </dependency> <!-- log4j 日志--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.20.0</version> </dependency>
-
MyBatis 默认使用 log4j 输出日志信息,所以可以配置日志文件 log4j.properties
### 设置日志级别和输出位置 ### log4j.rootLogger=DEBUG,console,logFile log4j.additivity.org.apache=true # 输出到控制台(console) log4j.appender.console=org.apache.log4j.ConsoleAppender # 输出级别最低为DEBUG log4j.appender.console.Threshold=DEBUG # 表示所有消息都会被立即输出 log4j.appender.console.ImmediateFlush=true #输出日志到控制台的方式 log4j.appender.console.Target= System.out # 灵活布局输出日志 log4j.appender.console.layout=org.apache.log4j.PatternLayout #设定以怎样的格式显示消息。 log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] [%c] [%p] -%m%n
-
创建mybatis-config.xml配置文件
-
编写,加载映射文件mapper.xml
-
加载配置文件,创建SqlSessionFactory工厂,获取SqlSession
-
调用mapper映射文件中的SQL语句来执行CRUD操作
-
返回结果集的映射
2、核心组件
2.1、组件介绍
mybatis的核心组件主要有4部分
-
SqlSessionFactoryBuilder(构造器):它的作用仅仅用来生成 SqlSessionFactory,创建成功后就失去作用,所以用在生成 SqlSessionFactory 的方法中作为局部变量使用,用完即可回收。
它通过内部的build(InputStream inputStream, String environment, Properties properties) 创建SqlSessionFactory,使用流读取配置文件。
-
SqlSessionFactory:相当于数据库的连接池,主要是用 openSession() 创建SqlSession接口,所以需要存在MyBatis的整个应用中。一般是创建一个单例模式,生命周期等同MyBatis的生命周期
-
SqlSession:相当于Connection对象,用于业务请求,用完关闭。存在于一次业务请求中,处理完请求后使用close方法关闭。
-
SQL Mapper(映射器):Mapper是通过SqlSession获取的,所以Mapper的生命周期小于SqlSession,它相当于一个请求中的业务处理。
2.2、SqlSessionFactory
SqlSessionFactory是 Mybatis的核心组件,每个MyBatis都是以一个SqlSessionFactory实例为中心,而它的作用就是生产SqlSession。
使用MyBatis需要配置 SqlSessionFactory 对象,通常使用 SqlSessionFactoryBuilder 的 build() 方法,SqlSessionFactory作为一个工厂接口,唯一的作用就是用来创建SqlSession的,所以我们一般会用单例模式来处理它。而一般选用通过XML配置的方式来生成 SqlSessionFactory。
-
创建database.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/库名?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=密码
-
创建一个映射器文件
<?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接口 --> <mapper namespace="com.yu.mybatis.mapper.UserMapper"> <!--数据库字段和Java属性之间的映射关系 --> <resultMap id="BaseResultMap" type="com.yu.mybatis.entity.User"> <id column="user_id" jdbcType="INTEGER" property="userId" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="deleted" jdbcType="BIT" property="deleted" /> </resultMap> <!--数据库的字段名字 --> <sql id="Base_Column_List"> user_id, username, `password`, deleted </sql> <!--根据id获取数据 --> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from `user` where user_id = #{userId,jdbcType=INTEGER} </select> </mapper>
-
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 文件--> <properties resource="database.properties"/> <!--配置数据库连接,可以配置多种环境,默认使用的环境(比如:default="development") --> <environments default="development"> <environment id="development"> <!--事务管理器的配置(比如:type="JDBC")。 JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。 MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期 --> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--指定映射器XML文件的路径--> <mappers> <mapper resource="mapping/UserMapper.xml"/> </mappers> </configuration>
-
创建一个帮助类,读取 xml 配置文件,生成SqlSessionFactory
public class SqlSessionUtil { private static SqlSessionFactory factory = null; static { try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); factory = new SqlSessionFactoryBuilder().build(in); } catch (IOException e) { e.printStackTrace(); } } public static SqlSessionFactory getFactory(){ return factory; } }
2.3、SqlSession
SqlSession是MyBatis的核心接口,其有两个实现类:其中DefaultSqlSession是单线程,SqlSessionManager是多线程。
它就相当于Connection对象,代表一个连接数据库的资源,每次需要获取和关闭。它提供了面向数据库执行 SQL 命令所需的所有方法,可以通过 SqlSession 实例直接运行已映射的 SQL 语句。
//SqlSession的获取,
//通过SqlSession对象所操作的sql都必须手动提交或回滚事务
SqlSession sqlSession = SqlSessionUtil.getFactory().openSession();
//此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = SqlSessionUtil.getFactory().openSession(true);
SqlSession执行SQL的两种方式
-
直接发送SQL给数据库,利用XML映射器
String statement=“XML映射文件的namespace属性值(Mapper接口全限定名) + XML映射文件方法的id名(mapper接口的方法名)”
Object parameter:插入所需的参数,通常是对象或者Map
RowBounds rowBounds:用于分页,它的两个属性: offset指查询的当前页数; limit指当前页显示多少条数据。
String mapKey:执查询结果会被封装成一个Map集合返回,key就是参数mapKey传入的列名,value 是封装的对象。
ResultHandler handler:ResultHandler对象用来处理查询返回的复杂结果集,通常用于多表查询。public interface SqlSession extends Closeable { /**查询方法,查询结果只有一条数据时才使用。*/ <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); /**查询方法,返回执行SQL话句查询结果的泛型对象的集合。。*/ <E> List<E> selectList(String statement); <E> List<E> selectList(String statement, Object parameter); <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); /**查询方法,根据结果对象中的一个属性将结果列表转换为 Map */ <K, V> Map<K, V> selectMap(String statement, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); /**Cursor 提供与 List 相同的结果,只是它使用 Iterator 懒惰地获取数据。*/ <T> Cursor<T> selectCursor(String statement); <T> Cursor<T> selectCursor(String statement, Object parameter); <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds); /**使用ResultHandler检索从语句键和参数映射的单行。 */ void select(String statement, Object parameter, ResultHandler handler); void select(String statement, ResultHandler handler); void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); /**插入方法,返回执行SQL语句所影响的行数。。*/ int insert(String statement); int insert(String statement, Object parameter); /**执行更新语句。将返回受影响的行数 */ int update(String statement); int update(String statement, Object parameter); /**执行删除语句。将返回受影响的行数。*/ int delete(String statement); int delete(String statement, Object parameter); /**提交事务。 */ void commit(); void commit(boolean force); /**回滚事务 */ void rollback(); void rollback(boolean force); /**刷新批处理语句*/ List<BatchResult> flushStatements(); /**关闭会话 */ @Override void close(); /**清除本地会话缓存。*/ void clearCache(); /**检索当前配置。 */ Configuration getConfiguration(); /**检索映射器*/ <T> T getMapper(Class<T> type); /**检索内部数据库连接 */ Connection getConnection(); }
-
获取Mapper接口,操作SQL(建议使用)
sqlSession.getMapper(***Mapper.class); //创建的Mapper接口
在使用Mybatis时,会根据映射文件创建相应的mapper接口,可以用接口的方法
public interface UserMapper { User selectByPrimaryKey(Integer userId); }
<!--对应mapper接口--> <mapper namespace="com.yu.mybatis.mapper.UserMapper"> <!-- id对应接口内方法名--> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> <!--@mbg.generated--> select <include refid="Base_Column_List" /> from `user` where user_id = #{userId,jdbcType=INTEGER} </select> </mapper>
2.4、映射器
MyBatis中最重要也是最复杂的组件,MyBatis是由一个Mapper接口,和一个XML映射文件(注解),对应以前的dao接口和一个dao实现类,它主要的作用是:
- 描述映射规则
- 提供SQL语句
- 配置缓存
- 提供动态SQL
2.4.1 接口和XML映射
在MyBatis中数据的一张表,分别对应一个实体类,一个Mapper接口、和一个XML的映射文件。
在IDEA中可以引入插件MyBatisCodeHelperPro,使用插件辅助创建所需的类。
-
实体类:根据数据表对应的实体类会自动创建好,并生成get和det方法。
-
mapper:接口,编写查询所有的方法,如果映射的路径正确,点击创建,会自动在xml文件中创建好
-
mapping:mapper的实现类,xml形式配置。
它的映射规则是:
先通过 mapper namespace=“com.yu.mapper.UserMapper” 映射到对应的接口
然后通过select、update、delete、inser这些标签的 id 属性值映射得到接口对应的方法上
再通过 resultMap、parameterType这两个属性可以将获得的结果集,映射到对应的实体类上进行自动装配。
<?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接口的全限定名--> <mapper namespace="com.yu.mapper.UserMapper"> <!--数据库的表和是实体类的映射关系,可以自己配置属性是另外一个实体的情况 --> <resultMap id="BaseResultMap" type="com.yu.pojo.User"> <id column="user_id" jdbcType="INTEGER" property="userId" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="deleted" jdbcType="BIT" property="deleted" /> </resultMap> <!--数据表的字段,在本XML中可直接引用--> <sql id="Base_Column_List"> user_id, username, `password`, deleted </sql> <!--和Mapper接口中的方法对应,select为查询属性, id为方法名, resultType为返回值--> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> <!--@mbg.generated--> select <include refid="Base_Column_List" /> from `user` where user_id = #{userId,jdbcType=INTEGER} </select> <!-- 插入--> <insert id="insert" keyColumn="user_id" keyProperty="userId" parameterType="com.yu.pojo.User" useGeneratedKeys="true"> <!--@mbg.generated--> insert into `user` (username, `password`, deleted) values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{deleted,jdbcType=BIT}) </insert> <!--删除--> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from `user` where user_id = #{userId,jdbcType=INTEGER} </delete> <!--修改--> <update id="updateByPrimaryKeySelective" parameterType="com.yu.pojo.User"> update `user` <set> <if test="username != null"> username = #{username,jdbcType=VARCHAR}, </if> <if test="password != null"> `password` = #{password,jdbcType=VARCHAR}, </if> <if test="deleted != null"> deleted = #{deleted,jdbcType=BIT}, </if> </set> where user_id = #{userId,jdbcType=INTEGER} </update> </mapper>
2.4.2 发送SQL
通过SqlSession可以获取Mapper接口,它对比SqlSession发送的SQL语句,有很高的可读性,因为SqlSession需要使用自己的方法通过SQL的id属性去匹配相应的SQL,而Mapper则是直接使用自己定义的方法。
public class SqlSessionTest {
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtil.getFactoryBean().openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectByPrimaryKey(1);
sqlSession.commit();
System.out.println(user.getUsername());
}
}
配置的日志打印的信息:
查到了数据
2.4.3、注解的形式
映射器中也有相应的注解来代替XML映射文件,来实现SQL语句,如果和XML同时定义,XML会覆盖掉注解.
public interface UserMapper {
@Select("select user_id, username, `password`, deleted from `user` where user_id =#{userId}")
User selectByPrimaryKey(Integer userId);
}
上面的这条注解语句可以代替下面这个:
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
<!--@mbg.generated-->
select
<include refid="Base_Column_List" />
from `user`
where user_id = #{userId,jdbcType=INTEGER}
</select>
虽然注解的形式要简单很多,但是很多的 sql语句的复杂性是很高的,这种复杂的sql语句放在XML文件中更适合,而且他还可能牵扯到一些动态 SQL等问题,所以还是推荐使用XML的方式。
2.5、执行器
MyBatis整体操作无非就是:连接数据库、编译生成SQL语句、执行SQL、获取数据进行封装等几个步骤,其中执行器 Executor就是用来执行SQL语句的,存在于SqlSession中。
MyBatis有三种基本的执行器:
- 简单执行器SimpleExecutor:每次执行update或select都会开启一个statement对象,用完立刻关闭statement对象
- 可重用执行器ReuseExecutor:执行update或select前,以sql作为key查找Statement对象,存在就使用不存在就创建,用完后不关闭,放置在Map中供下一次使用。(重复使用)
- 批量执行器BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.6、mybatis流程
- 读取MyBatis的核心配置文件:读取配置数据库的连接、属性、类别名、映射器等信息。
- 加载映射文件:Mapping.xml映射文件,用来配置操作数据库的SQL语句,可以通过xml和注解获取
- 构造会话工厂获取SqlSessionFactory:通过SqlSessionFactoryBuilder对象构建
- 创建会话对象SqlSession:包含了执行SQL的一些方法
- Executor执行器:mybatis的核心,用来SQL语句的生成和查询缓存的维护
- Statement对象:对SQL语句的封装,一个对象对应XML文件中的一个sql语句标签。
- 输入参数映射:传递参数,进行预编译处理
- 封装结果集:将SQL语句产生的数据进行封装