【JavaLearn】#(26)MyBatis基础:认识框架、MyBatis环境搭建、基本CRUD、配置文件、日志管理、别名、属性文件、ThreadLocal保存sqlSession、本地DTD、模板

1. MyBatis入门

是一个基于 Java 的持久层框架,将接口和 Java的 POJOs(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录 【ORM】

1.1 认识框架

框架(Framework)是一个框子—约束性,也是一个架子—支撑性。框架类似于一个毛坯房,由开发人员在毛坯房基础上进行装修

使用框架可以减少开发时间、降低开发难度、保证设计质量,还可以起到约束,可以降低程序员之间沟通和日后维护的成本

1.2 ORM

JDBC缺点:需要手动的完成面向对象的Java语言和面向关系的数据库之间数据的转换(得到了一个结果集,将数据挨着取出来封装到一个对象中)

ORM(Object-Relational Mapping)对象关系映射:在关系型数据库对象之间做一个映射,就可以像操作对象一样操作数据库了

image-20220430183108733

持久化:将内存中的数据保存到关系型数据库

持久层:专注于实现数据持久化应用领域(内存到数据库,数据库到内存等)的某个特定系统的一个逻辑层面

1.3 认识 MyBatis

image-20220430213832058

MyBatis 是一个半自动 ORM框架(SQL语句由用户书写),其本质是对 JDBC的封装。使用 MyBatis 重点需要程序员编写 SQL,不需要写JDBC代码

Hibernate 是一个全自动的ORM框架(SQL语句、解析、执行全由Hibernate完成),不需要手写SQL,但是缺少了灵活性,SQL的调优也比较麻烦

2. MyBatis快速上手

2.1 搭建项目环境

项目总体框架

image-20220501095434991

  • 引入 jar包(此处手动导入[从MyBatis官网下载]、后期使用maven自动导入即可)

    image-20220501095511443

  • 准备配置文件 (mybatis-cfg.xml 官方源码配置文件的名字)----- 位置在 src 包下

    <?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>
        <!-- 配置参数(log4j使用) -->
        
        <!-- 环境参数配置 -->
        <environments default="mysql">
            <environment id="mysql">
                <!-- 配置事务管理器 -->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 数据源:连接池类型的 -->
                <dataSource type="POOLED">
                    <!-- name的值相当于是固定的 -->
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        
      	<!-- 告诉 MyBatis 映射文件的位置(映射文件使用) -->
    </configuration>
    

    注意

    • XML文件需要提供dtd或者xsd文件,来定义XML文件的标签结构
    • 数据库四个连接参数的name属性的值来自org.apache.ibatis.datasource.unpooled包(mybatis的类包)下类UnpooledDataSource
  • 准备映射文件

    <?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.lwclick.mapper.EmpMapper">
    
    </mapper>
    
  • 配置文件 mybatis-cfg.xml 中,指定映射文件的位置

    <!-- 告诉 MyBatis 映射文件的位置 -->
    <mappers>
        <mapper resource="com/lwclick/mapper/EmpMapper.xml"></mapper>
    </mappers>
    
  • 引入 log4j 进行功能测试(请使用最新版,避免漏洞)

    • 配置文件中,加入配置

      <!-- 配置参数 -->
      <settings>
          <!-- 指定所使用的日志框架 -->
          <setting name="logImpl" value="LOG4J"/>
      </settings>
      
    • 创建 log4j 的配置文件 ----- 位置在 src 包下

      # 定义日志的全局级别
      log4j.rootLogger=error,stdout,logfile
      
      # 设置日志的局部级别
      # 如果访问该包下的资源,日志级别精确到 debug级
      log4j.logger.com.lwclick.mapper=debug
          # 接口级别日志
          # log4j.logger.com.lwclick.mapper.EmpMapper=debug
          # 方法级别日志
          # log4j.logger.com.lwclick.mapper.EmpMapper.findBy
      
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.Target=System.err
      log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
      
      log4j.appender.logfile=org.apache.log4j.FileAppender
      log4j.appender.logfile.File=d:/sys.log
      log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
      log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
      

2.2 完成 select操作

首先需要先创建实体类 Employee:

public class Employee {
    private int empno;
    private String ename;
    private String job;
    private Date hireDate;
    private double sal;
	// getter / setter / toString() / 有参,无参构造方法
}

环境搭建完成后,完成 select操作,只涉及到映射文件测试类

2.2.1 查询所有操作

映射文件

<!-- 返回值如果是 List,resultType 要写集合的元素的类型,也就是泛型类型 -->
<select id="selectAll" resultType="com.lwclick.entity.Employee">
    SELECT * FROM emp
</select>

测试类(使用 JUnit 测试框架):

public class TestEmployee {
    // JUnit 的 @Test注解
    @Test
    public void testSelectAll() throws IOException {
        // 1. 创建 SqlSessionFactory
        InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

        // 2. 使用 SqlSessionFactory创建一个 SqlSession
        SqlSession sqlSession = factory.openSession();

        // 3. 完成数据库操作并得到结果
        List<Employee> list = sqlSession.selectList("com.lwclick.mapper.EmpMapper.selectAll");  // 映射文件的 namespace名 + SQL语句的 id

        // 4. 关闭资源
        sqlSession.close();

        // 5. 输出结果
        list.forEach(System.out::println);
    }
}

常见异常

  • 配置文件中的mappers中没有指定映射文件的路径或者namespace+id写错了
    • Cause: java.lang.IllegalArgumentException:Mapped Statements collection does not contain value for com.lwclick.mapper.EmpMapper.selectAll
  • 缺少了ResultType或者ResultMap的指定
    • Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘com.lwclick.mapper.EmpMapper.selectAll’. It’s likely that neither a Result Type nor a Result Map was specified.

2.2.2 通过id查询

映射文件

<select id="selectById" parameterType="int" resultType="com.lwclick.entity.Employee">
    SELECT * FROM emp WHERE empno = #{param1}
</select>

测试类

@Test
public void testSelectById() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    SqlSession sqlSession = factory.openSession();

    // 7839为参数
    Employee employee = sqlSession.selectOne("com.lwclick.mapper.EmpMapper.selectById", 7839);

    sqlSession.close();

    System.out.println(employee);
}

2.2.3 通过参数查询

映射文件

<!-- 如果参数是一个 JavaBean,会根据参数拼接 【getter方法】 并调用 -->
<select id="selectEmpByParams" parameterType="com.lwclick.entity.Employee" resultType="com.lwclick.entity.Employee">
    SELECT * FROM emp WHERE job = #{job} AND sal >= #{sal}
</select>

测试类

@Test
public void testSelectEmpByParams() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    SqlSession sqlSession = factory.openSession();

    Employee emp = new Employee();
    emp.setJob("CLERK");
    emp.setSal(3000);
    List<Employee> list = sqlSession.selectList("com.lwclick.mapper.EmpMapper.selectEmpByParams", emp);

    sqlSession.close();

    list.forEach((e) -> {
        System.out.println(e);
    });
}

2.2.4 总结

映射文件中细节:

  • 每个SQL语句的 id必须唯一

  • parameterType(参数类型),可以省略,MyBatis会自动推断类型; 如果提供,类型必须正确

    取值只能是一个类型,所以后续都省略此参数

  • resultType(返回值类型)

    • 返回值是集合,取值为集合的元素泛型类型完整路径名
    • 返回值是对象,取值为对应类完整路径名
  • 方法参数的传递

    • 参数是基本类型(目前sqlSession这种方式),使用 #{param1}
    • 参数是引用数据类型,使用 #{属性名} 接收数据,底层调用的是其getter方法如果没有getter方法,就会直接找同名属性

自动映射(Auto Mapping):

  • 按照名称自动把结果集中的数据映射到对象属性中(如果某个属性没有 setter方法,那就直接找同名的属性
  • 如果名称不一样,最简单的是MySQL别名的方式, select sal as salary,保证与实体类中的属性名称一致

2.3 完成DML操作

2.3.1 insert 操作

注意事项

  • DML操作的底层调用executeUpdate(),返回值都是int类型,无需指定
  • 执行DML操作,默认事务手动提交,此时有两种解决方式
    • 使用自动提交
    • 手动执行 commit() 或者 rollback() 结束事务推荐

映射文件

<!-- DML操作,底层调用 executeUpdate(),返回值都是 int,无需指定 -->
<insert id="saveEmp">
    INSERT INTO emp VALUES (null, #{ename}, #{job}, #{hireDate}, #{sal})
</insert>

测试类

@Test
public void testSaveEmp() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    // 自动事务提交   SqlSession sqlSession = factory.openSession(true);
    SqlSession sqlSession = factory.openSession();

    Employee emp = new Employee("zhangsan", "clerk", Date.valueOf("1999-12-03"), 3000);
    int n = sqlSession.insert("com.lwclick.mapper.EmpMapper.saveEmp", emp);
    
    // 手动提交事务!!!!!
    sqlSession.commit();

    sqlSession.close();

    System.out.println(n);
}

2.3.2 update操作

映射文件

<update id="updateEmp">
    UPDATE emp SET job = #{job}, sal = #{sal} WHERE empno = #{empno}
</update>

测试类

@Test
public void testUpdateEmp() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    SqlSession sqlSession = factory.openSession();

    Employee emp = new Employee();
    emp.setJob("clerk");
    emp.setSal(5000);
    emp.setEmpno(9839);
    int n = sqlSession.update("com.lwclick.mapper.EmpMapper.updateEmp", emp);

    // 手动提交事务
    sqlSession.commit();

    sqlSession.close();

    System.out.println(n);
}

2.3.3 delete操作

映射文件

<delete id="deleteEmp">
    DELETE FROM emp WHERE empno = #{param1}
</delete>

测试类

@Test
public void testDeleteEmp() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-cfg.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

    SqlSession sqlSession = factory.openSession();

    int n = sqlSession.delete("com.lwclick.mapper.EmpMapper.deleteEmp", 7839);

    // 手动提交事务
    sqlSession.commit();

    sqlSession.close();

    System.out.println(n);
}

2.3.4 总结

其实一个SqlSession的update、delete、insert中任意一个方法均可完成所有DML操作。底层都是调用的update方法,就好比JDBC中的executeUpdate()可以完成DML操作一样。

image-20220501123900352

3. MyBatis功能详解

3.1 关于 jar包

编号元素列表作用
1mybatis-3.5.2.jarMybatis核心jar包(包含全部的功能)
2ant-1.10.3.jar; ant-launcher-1.10.3.jar将软件编译、测试、部署等步骤联系在一起加以自动化的一个软件构建工具
3asm-7.0.jar代码生成,代码混淆,代码转换等等以字节码为操作目标的工作,一定程度上类似javac的功能
4cglib-3.2.10.jar实现动态代理的技术,延迟加载时使用
5javassist-3.24.1-GA.jar可用来检查、”动态”修改及创建 Java类。功能与JDK自带反射功能类似,但比反射功能更强大
6ognl-3.2.10.jar对象导航图语言的缩写,功能强大的表达式语言工具包。在动态SQL和${param}中使用
7commons-logging-1.2.jar日志包
8slf4j-api-1.7.26.jar日志包
9slf4j-log4j12-1.7.26.jar日志包
10log4j-1.2.17.jar日志包
11log4j-api-2.11.2.jar日志包
12log4j-core-2.11.2.jar日志包

3.2 核心 API

  • SqlSessionFactoryBuilder: 根据配置或者代码生成SqlSessionFactory(将各个工厂生产的东西组装起来),采用的是分步构建的构建者模式
    • 当SqlSessionFactory创建后,该对象就没有存在的必要了
  • SqlSessionFactory:生产SqlSession,使用的是工厂模式
    • 在应用执行期间一直存在,需要一直生产SqlSession
  • SqlSession:可获取Mapper的接口,也可以发送SQL语句并返回结果(相当于Connection)
    • 每个线程都有自己的 SqlSession实例,是线程不安全的,不能被共享
  • Mapper:由一个Java接口XML文件(或者注解)构成,需要给出对应的SQL和映射规则,负责发送SQL去执行并返回结果
    • 关闭了 SqlSession也就关闭了由其所产生的Mapper

image-20220501145641908

3.3 关于配置文件

<?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>
    <!-- 配置参数 -->
    <settings>
        <!-- 指定所使用的日志框架 -->
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <environments default="mysql">
        <environment id="mysql">
            <!-- 配置事务管理器 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 数据源:连接池类型的 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 告诉 MyBatis 映射文件的位置 -->
    <mappers>
        <mapper resource="com/lwclick/mapper/EmpMapper.xml"></mapper>
    </mappers>
</configuration>

mybatis-cfg.xml 配置文件中,配置项的顺序是有规定的,在 dtd文件中进行了规定

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

environment配置项中的配置参数

  • transactionManager(事务管理器,有两种取值 JDBC | MANAGED)

    • JDBC:简单使用了 JDBC 的提交和回滚设置。它依赖于从数据源得到的连接来管理事务范围。
    • MANAGED:几乎没做什么,从来不提交或回滚一个连接。会让容器(如Spring)来管理事务的整个生命周期

    image-20220501151006211

  • dataSource(数据源,有三种取值 UNPOOLED | POOLED | JNDI)

    • UNPOOLED:每次请求都要打开连接,不用就关闭,未采用连接池

      • driver、url、username、password
      • defaultTransactionIsolationLevel(默认的连接事务隔离级别)

      image-20220501152613177

      注意:由上图可知,property 的name取值,必须是图中的属性名

    • POOLED:连接池方式(目前就采用这种)

      • poolMaximumActiveConnections:在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
      • poolMaximumIdleConnections:任意时间可能存在的空闲连接数
      • 其他一些参数。。。

      image-20220501152728813

    • JNDI:使用 Spring这类容器时,容器可以集中或在外部配置数据源

mappers配置项的配置

  • mapper:告诉MyBatis 去哪找映射文件
    • resource:资源文件的地址,使用 / 不是用 . ( resource=“com/lwclick/mapper/EmpMapper.xml” )
    • url:全限定路径,资源文件在本地的绝对路径,以file开始
    • class:接口的全限定地址,使用 . 不是用 / (class=“com.lwclick.mapper.EmpMapper”) — EmpMapper为接口
  • package:包下的所有接口,不用再一个个的指定(必须是接口
    • name:包的名字( name=“com.lwclick.mapper” )

3.4 关于日志管理

MyBatis 的内置日志工厂(LogFactory)提供日志功能,内置日志工厂将日志交给以下某一个工具做代理(顺序查找)

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging
  • NO_LOGGING

image-20220501155326582

在项目中把日志工具环境配置好之后,不用在MyBatis进行配置也可以让日志生效,最好还是使用 setting进行配置(防止使用默认的日志框架)

<settings>
    <!-- 指定所使用的日志框架 -->
    <setting name="logImpl" value="LOG4J"/>
</settings>

其中 setting 的name的取值是固定的

image-20220501160040154

4. 功能的完善

4.1 定义别名

4.1.1 单个文件配置

mybatis-cfg.xml 配置文件中,使用 typeAliases 定义别名

<!-- 定义类型的别名 -->
<typeAliases>
    <typeAlias type="com.lwclick.entity.Employee" alias="employee"></typeAlias>
</typeAliases>

那么在映射文件的 resultType中,就可以直接使用 employee了

<select id="selectAll" resultType="employee">
    SELECT * FROM emp
</select>

4.1.2 通过包配置

mybatis-cfg.xml

<typeAliases>
    <!-- 通过 package指定的别名,自动是类名的首字母小写形式 -->
    <package name="com.lwclick.entity"/>
</typeAliases>

映射文件

<select id="selectAll" resultType="employee">    <!-- 通过 package指定的别名,自动是类名的首字母小写形式 -->
    SELECT * FROM emp
</select>

4.1.3 系统内置别名

别名映射的类型别名映射的类型别名映射的类型
_bytebytestringStringdateDate
_longlongbyteBytedecimalBigDecimal
_shortshortlongLongbigdecimalBigDecimal
_intintshortShortobjectObject
_integerintintIntegermapMap
_doubledoubleintegerIntegerhashmapHashMap
_floatfloatdoubleDoublelistList
_booleanbooleanfloatFloatarraylistArrayList
booleanBooleancollectionCollection
iteratorIterator

系统内置的别名设置都是在 org.apache.ibatis.type.TypeAliasRegistry 类中指定并注册的

image-20220501161512136

4.1.4 配置文件中的别名

配置文件中value的取值,LOG4J,POOLED,JDBC等,都是在 org.apache.ibatis.session.Configuration 的无参数构造方法中指定的

image-20220501161809516

4.2 引入属性文件

在项目中增加 jdbc.properties 文件(src目录下)

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
user=root
pwd=root

配置文件中,进行修改

<!-- 增加 properties 配置指定配置文件名称 -->
<properties resource="jdbc.properties"></properties>

<!-- 其他配置项 -->

<!-- 环境配置 -->
<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="JDBC"></transactionManager>
        <dataSource type="POOLED">
            <!-- 修改 value 的值 -->
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${user}"/>
            <property name="password" value="${pwd}"/>
        </dataSource>
    </environment>
</environments>

4.3 使用ThreadLocal保存sqlSession

开发时会碰到复杂的业务包含多个数据库操作,比如下订单,涉及到库存修改、主订单添加、订单明细添加、转账等多个数据库操作

此时必须保证这多个数据库操作是一个事务,保证是同一个sqlSession ------> 使用 ThreadLocal 保存 sqlSession

image-20220501164708624

增加一个 DBUtil 工具类

public class DBUtil {
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();

    static SqlSessionFactory factory = null;
    static {
        InputStream is = null;
        try {
            is = Resources.getResourceAsStream("mybatis-cfg.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 工厂创建一次即可
        factory = new SqlSessionFactoryBuilder().build(is);
    }

    /**
     * 获取 SqlSession
     * @return
     */
    public static SqlSession getSqlSession() {
        // 从 threadLocal中获取 sqlSession
        SqlSession sqlSession = threadLocal.get();

        if (sqlSession == null) { // 当前线程第一次获取 sqlSession
            // 创建一个sqlSession
            sqlSession = factory.openSession();

            // 放入 threadLocal中
            threadLocal.set(sqlSession);
        }

        return sqlSession;
    }

    /**
     * 关闭 SqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
            // 清空threadLocal
            threadLocal.set(null);
        }
    }
}

使用时,改为:

@Test
public void testDeleteEmp() throws IOException {
    SqlSession sqlSession = DBUtil.getSqlSession();  // 通过工具类获取

    int n = sqlSession.delete("com.lwclick.mapper.EmpMapper.deleteEmp", 7839);

    // 手动提交事务
    sqlSession.commit();

    DBUtil.closeSqlSession(sqlSession);

    System.out.println(n);
}

4.4 引入本地的dtd文件

在没有联网的情况下,让dtd约束继续起作用,并且出现标签提示,可以通过引入本地dtd文件来实现。

  • 下载dtd:http://mybatis.org/dtd/mybatis-3-config.dtd

  • 将dtd文件放到某个目录下

  • IDEA:File — Settings — Languages&Frameworks — Schemas and DTDs

    image-20220501172654658

4.5 IDEA创建文件的模板

IDEA 提供了大量的内置文件模板template,可以自定义模板,避免重复,提高效率

File — settings — editor — File and Code Templates

image-20220501173511142

下次新建内容时,直接选择该模板即可

image-20220501173614444

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LRcoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值