在以前我们都是使用原始的JDBC,但是这种连接会造成创建、释放频繁造成系统资源的浪费从而影响性能,当sql变化时需要去改变Java代码,不符合开闭的原则,在查询时要手动将结果集封装到实体,插入时手动的提取实体到占位符,增加了代码量。
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();
}
}
}
SpringJDBC是对原始JDBC做了封装,它将原始的JDBC的步骤做了封装,我们只需要完成相关配置,就可以直接连接数据库,在操作中只需要关注JdbcTemplate 对象即可。
<context:property-placeholder location="classpath:database.properties"/>
<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>
<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);
}
原始的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-Plus,它是在Mybatis的基础上做了一些增强,在不改变MyBatis的基础上,简化了开发。
Mybatis的使用过程:
添加mybatis的依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.7version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.9.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.20.0version>
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操作
返回结果集的映射
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,它相当于一个请求中的业务处理。
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=密码
创建一个映射器文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yu.mybatis.mapper.UserMapper">
<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>
<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配置数据库连接
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="database.properties"/>
<environments default="development">
<environment id="development">
<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>
<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;
}
}
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 namespace="com.yu.mybatis.mapper.UserMapper">
<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>

MyBatis中最重要也是最复杂的组件,MyBatis是由一个Mapper接口,和一个XML映射文件(注解),对应以前的dao接口和一个dao实现类,它主要的作用是:
在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这两个属性可以将获得的结果集,映射到对应的实体类上进行自动装配。
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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>
<sql id="Base_Column_List">
user_id, username, `password`, deleted
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
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">
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>
通过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());
}
}
配置的日志打印的信息:

查到了数据

映射器中也有相应的注解来代替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">
select
<include refid="Base_Column_List" />
from `user`
where user_id = #{userId,jdbcType=INTEGER}
select>
虽然注解的形式要简单很多,但是很多的 sql语句的复杂性是很高的,这种复杂的sql语句放在XML文件中更适合,而且他还可能牵扯到一些动态 SQL等问题,所以还是推荐使用XML的方式。
MyBatis整体操作无非就是:连接数据库、编译生成SQL语句、执行SQL、获取数据进行封装等几个步骤,其中执行器 Executor就是用来执行SQL语句的,存在于SqlSession中。
MyBatis有三种基本的执行器:
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;
}