老杜MyBatis笔记

1 MyBatis的概述

Java程序依靠Java数据库(JDBC)实现对数据库的操作,但JDBC操作数据库的代码写在Java程序中里面不符合OCP开闭原则,JDBC操作繁琐。故而需要学习MyBatis

O(object):jvm中的java对象

R(Relational):关系型数据库

M(Mapper):映射

图-1 ORM框架的工作原理

图-2 ORM框架的工作原理

2 Mybatis的入门程序

2.1 第一个MyBatis程序

2.1.1 resources目录

   放在这个目录当中的,一般都是资源文件,配置文件。直接放到resources目录下的资源,等同于放到了类的根路径下。

        

2.1.2 开发步骤

第一步:打包方式jar,在pom文件中

   <!--打包方式-->
    <packaging>jar</packaging>

第二步:引入依赖 mybatis和mysql

<dependencies>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
    </dependencies>

第三步:编写myatis核心配置文件:mybatis-config.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="development">
        <environment id="development">
                <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
    </mappers>
</configuration>

注意:

这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。只是大家都采用这个名字。

这个文字存放的位置不是固定的,可以随意,但一般情况下,会放到类的根路径下。

第四步:编写XxxxMapper.xml文件

在这个配置文件中编写sql语句

这个文件名不是固定的,放的位置也不是固定的,我们这里给它起个名字,叫做:CarMapper.xml。

这里使用insert插入语句中,id为null是因为数据库之前设计了id自增所以可以为null

<?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="暂时随便写">
    <insert id="inserCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,103,"小米",13.00,"2024-3-9","电车")
    </insert>
</mapper>

第五步:在mybatis-config.xml文件中指定XxxxMapper.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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--
        执行mapper.xml文件的路径
        resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
 <mappers>
        <!--
        执行mapper.xml文件的路径
        resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>

注意:resource属性会自动从类的根路径下开始查找资源。

第六步:编写Mybatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做CRUD)

在Mybatis当中,负责执行SQL语句的那个对象叫什么?

SqlSession

SqlSession是专门用来执行SQL语句的,是一个java程序和数据库之间的一次会话。

要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。

怎么获取SqlSessionFactory对象?

需要通过SqlSessoinFactoyrBuilder对象。

通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。

mybatis的核心对象包括:

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

//获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory对象
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(is); //一般情况下都是一个数据库对应一个SqlSessionFactory对象。
        //获取SqlSession对象
        SqlSession session=sqlSessionFactory.openSession();
        //执行SQL语句
        int count=session.insert("inserCar");
        System.out.println("插入了几条语句"+count);
        //使用SqlSession需要手动提交
        session.commit();

2.1.3 从xml文件中构建SqlSessionFactory

通过官方的这句话,你能想到什么呢?

在mybatis中一定是有一个重要的对象,这个对象是:SqlSessionFactory对象。

SqlSessionFactory对象的构建需要xml。

2.1.4 mybatis中有两个主要的配置文件

其中一个是:mybatis-config.xml,这个是核心配置文件,主要配置连接数据库的信息等。(一个)

另一个是:XxxxMapper.xml,这个文件是专门用来编写SQL语句的配置文件(一个表一个)

t_user表,一般会对应一个UserMapper.xml

t_student表,一般会对应一个StudentMapper.xml

2.1.5 关于第一个程序的小细节

mybatis中sql语句的结尾“;”可以省略

Resources.getResourceAsStream

 小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)

优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。

InputStream is=new FileInputStream(“d:\\mybatis-config.xml”)

采用这种方式也可以。

缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。

已经验证了:

mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其他名字。

mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其他位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。

 ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");

ClassLoader.getSystemCllassLoader()获取系统的类加载器

系统类加载器有一个方法叫做:getResourceAsStream

它就是从类路径当中加载资源的。

通过源代码分析发现:

InputStream is= Resources.getResourceAsStream("mybatis-config.xml");

底层的源代码其实就是:

InputStream is= ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");

CarMapper.xml文件的名字是固定的吗? CarMapper.xml文件的路径是固定的吗?

都不是固定的。

  <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
   <mapper url="路径"></mapper>这种方式是从绝对路径当中加载资源

2.2 MyBatis中的事务

 <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>

2.2.1 关于mybatis的事务管理机制。(深度解析)

在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理

  <transactionManager type="JDBC"/>

*type属性的值包括两个:

JDBC(jdbc)

MANAGED(managed)

type后面的值,只有以上两个值可选,不区分大小写。

在mybatis中提供了两种事务管理机制:

第一种:JDBC事务管理器。

第二种:MANAGED事务管理器。

JDBC事务管理器:

mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:

   conn.setAutoCommit(false);  开启事务
   conn.commit();  // 提交事务
   conn.rollback(); //回滚事务

使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。

如果你编写的代码是下面的代码:

 SqlSession session=sqlSessionFactory.openSession(true);

表示没有开启事务。因为这种方式压根不会执行

 conn.setAutoCommit(false);  开启事务

 如果你没有在JDBC代码中执行: conn.setAutoCommit(false);的话,默认的autoCommit是true。

在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。
如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。

MANAGED事务管理器:
mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如: spring.我不管事务了,你来负责吧。
对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。


*重点:
以后注意了,只要你的autoCommit是true,就表示没有开启事务。只有你的autoCommit是false的时候,就表示开启了事务。

2.2.2 认识junit单元测试

在公司,通常需要程序员自己写的业务功能需要自己去负责测试。如果一个类就一个mian方法是不实际的。所以需要一个junit做单元测试

新建简单的maven项目

pom文件中添加依赖

 <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

在src/main/java下建立MathService类

public class MathService {
    public int Sum(int a,int b){
        return a+b;
    }
}

在test/java下建立MathServiceTest

import org.junit.Assert;
import org.junit.Test;

public class MathServiceTest {  //名字规范: 你要测试的类名+Test
    //单元测试方法写多少个
    //一般是一个业务方法对应一个测试方法
    //测试方法的规范:public void testXxxx(){}
    //测试方法的方法名:以test开始。假设测试的方法是sum,这个测试方法名: testSum
    //@Test注解非常重要,被这个注解标注的方法就是一个单元测试方法。
    @Test
    public void testSum(){
        //单元测试中有两个重要的概念:
        //一个是:实际值(被测试的业务方法的真正执行结果)
        //一个是:期望值(执行了这个业务方法之后,你期望的执行结果是多少)
        MathService mathService=new MathService();
        //获取实际值
        int actual=mathService.Sum(1,2);
        //期望值
        int expected=3;
        //加断言进行测试
        Assert.assertEquals(expected,actual);
    }
}

2.2.3 使用junit单元测试

新建普通maven项目

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatis-001</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--打包方式-->
    <packaging>jar</packaging>

    <dependencies>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

在resources下建立mybatis-config.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="development">
        <environment id="development">
                <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

在resources下建立CarMapper.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="暂时随便写">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,103,"小米",9.99,"2024-03-10","电车")
    </insert>
</mapper>

在test/java下建立

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.Test;

import java.io.IOException;

public class CarMapperTest {




    @Test
    public void testInsertCar(){
        //编写mybatis程序
        SqlSession sqlSession=null;
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        try {
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));
            //开启会话(底层会开启事务)
            sqlSession=sqlSessionFactory.openSession();
            //执行SQL语句,处理相关业务
            int count=sqlSession.insert("insertCar");
            System.out.println(count);
            //执行到这里,没有发生任何异常,提交事务。终止事务。
            sqlSession.commit();
        } catch (IOException e) {
            //最好回滚事务
            if(sqlSession!=null){
                sqlSession.rollback();
            }
            e.printStackTrace();
        }finally{
            //关闭会话(释放资源)
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
    }



}

2.2.4 日志

关于mybatis集成日志组件。让我们调试起来更加方便。

*mybatis常见的集成的日志组件有哪些呢?
SLF4J(沙拉风)
LOG4J
LOG4J2
STDOUT_LOGGING

....

*其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。

 <settings>
        <setting name="logImpt" value="STDOUT_LOGGING"/>
    </settings>

这个标签在编写的时候要注意,它应该出现在environments标签之前。注意顺序。当然,不需要记忆这个顺序。因为有dtd文件进行约束呢。我们只要参考dtd约束即可。

这种实现也是可以的,可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。

*集成logback日志框架。
logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)第一步:引入logback的依赖。


        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.14</version>
        </dependency>

|第二步:引入logback所必须的xml配置文件。
这个配置文件的名字必须叫做: logback.xml或者logback-test.xml,不能是其它的名字。这个配置文件必须放到类的根路径下。不能是其他位置。

主要配置日志输出相关的级别以及日志具体的格式。

<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置文件修改时重新加载,默认true -->
<configuration debug="false">

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8">
            <!-- 输出日志记录格式 -->
            <pattern>[%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--    mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>


    <!-- 日志输出级别,LOGBACK日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR-->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

2.2.5 封装代码做uitl工具类

在src/java下建立创建SqlSessionUtil.class

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 java.io.IOException;

public class SqlSessionUtil {

    //工具类的构造方法一般都是私有化的。
    //工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
    //为了防止new对象,构造方法私有化。
    private SqlSessionUtil(){

    }

    private static SqlSessionFactory sqlSessionFactory;

    //类加载时执行
    //sqLSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象
    static{
        try {
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取会话对象
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
}

重新对业务进行测试

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;


public class CarMapperTest {
    
    @Test
    public void testInsertCar(){
        SqlSession sqlSession=SqlSessionUtil.openSession();
            //执行SQL语句,处理相关业务
            int count=sqlSession.insert("insertCar");
            System.out.println(count);
            sqlSession.commit();
            sqlSession.close();
    }
}

3 完成简单的CRUD

3.1 什么是CRUD

C: Create增
R: Retrieve查(检索)
U: Update改
D: Delete删

3.2 insert

  <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,103,"小米",9.99,"2024-03-10","电车")
    </insert>

这样写的问题是?
显然是写死到配置文件中的。这个在实际开发中是不存在的。
一定是前端的form表单提交过来数据。然后将值传给sql语句。 

例如:JDBC的代码是怎么写的?
String sql = "insert into t_car(id,canr_num, buand,guide_price,produce_time , car_type) values(null,? ,?, ? ,? ,?)";

ps.setString(1,xxx);
ps.setString(2, yvy);

在JDBC当中占位符采用的是?,在mybatis当中是什么呢?
和?等效的写法是:#{}
在mybatis当中不能使用?占位符,必须使用#(}来代替JDBC当中的?

  <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,#{},#{},#{},#{},#{})
    </insert>

3.3 实操

在pom文件中添加依赖,刷新maven

 <dependencies>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--logback依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.14</version>
        </dependency>
    </dependencies>

添加以下文件

添加mybatis-config.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>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
                <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

添加logback.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>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
                <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

添加CarMapper.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="暂时随便写">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,103,"小米",9.99,"2024-03-10","电车")
    </insert>
</mapper>

添加SqlSessionUtil

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 java.io.IOException;

public class SqlSessionUtil {

    //工具类的构造方法一般都是私有化的。
    //工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
    //为了防止new对象,构造方法私有化。
    private SqlSessionUtil(){

    }

    private static SqlSessionFactory sqlSessionFactory;

    //类加载时执行
    //sqLSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象
    static{
        try {
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取会话对象
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
}

在test/java下创建CarMapperTest

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class CarMapperTest {



    @Test
    public void testInsertCar(){
        SqlSession session=SqlSessionUtil.openSession();
        //假设前端传递数据
        //可以用map进行封装
        Map<String, Object> map=new HashMap<>();
        map.put("carNum","1004");
        map.put("brand","比亚迪秦");
        map.put("guidePrice",10.0);
        map.put("produceTime","2024-03-13");
        map.put("carType","电车");

        //执行SQL语句
        //insert方法的参数
        //第一个参数: sqlId,从CarMapper.xml文件中复制。
        // 第二个参数:封装数据的对象。
        int count=session.insert("insertCar",map);
        System.out.println(count);
        session.commit();
        session.close();
    }
}

修改CarMapper.xml文件

 <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
 </insert>

java程序中使用Map可以给SQL语句的占位符传值:

   Map<String, Object> map=new HashMap<>();
        map.put("carNum","1004");
        map.put("brand","比亚迪秦");
        map.put("guidePrice",10.0);
        map.put("produceTime","2024-03-13");
        map.put("carType","电车");

 map中的key对应了insert标签中#{key}

 <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
 </insert>

注意:#{这里写什么?写map集合的key,如果key不存在,获取的是null}

3.4 封装汽车相关信息的pojo类

在src/java下创建Car类

public class Car {
    private Long id;
    private  String carNum;
    private String brand;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Car() {
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", carNum='" + carNum + '\'' +
                ", brand='" + brand + '\'' +
                ", guidePrice=" + guidePrice +
                ", produceTime='" + produceTime + '\'' +
                ", carType='" + carType + '\'' +
                '}';
    }

    public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
        this.id = id;
        this.carNum = carNum;
        this.brand = brand;
        this.guidePrice = guidePrice;
        this.produceTime = produceTime;
        this.carType = carType;
    }

    public String getCarNum() {
        return carNum;
    }

    public void setCarNum(String carNum) {
        this.carNum = carNum;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Double getGuidePrice() {
        return guidePrice;
    }

    public void setGuidePrice(Double guidePrice) {
        this.guidePrice = guidePrice;
    }

    public String getProduceTime() {
        return produceTime;
    }

    public void setProduceTime(String produceTime) {
        this.produceTime = produceTime;
    }

    public String getCarType() {
        return carType;
    }

    public void setCarType(String carType) {
        this.carType = carType;
    }

    private  Double guidePrice;
    private String produceTime;
    private String carType;
}

在CarMapperTest中添加新的测试

    @Test
    public void testInsertCarPojo(){
        SqlSession session=SqlSessionUtil.openSession();
        Car car=new Car(null,"1005","宝马",36.6,"2024-03-13","油车");
        //执行SQL语句
        //insert方法的参数
        //第一个参数: sqlId,从CarMapper.xml文件中复制。
        // 第二个参数:封装数据的对象。
        int count=session.insert("insertCar",car);
        System.out.println(count);
        session.commit();
        session.close();
    }

java程序中使用POJ0类给SQL语句的占位符传值:

 Car car=new Car(null,"1005","宝马",36.6,"2024-03-13","油车");

注意:占位符#{},大括号里面写:pojo类的属性名

<insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values (null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>

出现了什么问题呢?

There is no getter for property named 'xyz' in 'class com. powernode.mybatis.pojo.Car '

mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。

怎么解决的?
可以在Car类中提供一个getXyz()方法。这样问题就解决了。

通过这个测试,得出一个结论:
严格意义上来说:如果使用POJ0对象传递值的话,#这个大括号中到底写什么?

严格意义上来说:如果使用POJ0对象传递值的话,#(这个大括号中到底写什么?
写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。例如:getUsername() --> #{username}
例如: getEmail() --> #{email}

也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?
调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()

3.5 delete

*需求:根据id删除数据

注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。

  <delete id="deleteById">
        delete  from t_car where  id=#{id}
    </delete>
    

    @Test
    public void testDeleteById(){
        SqlSession sqlSession=SqlSessionUtil.openSession();
        //执行SQL语句
        int count=sqlSession.delete("deleteById",9);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

3.6 update

*需求:根据id修改某条记录。

 <update id="updateById">
        update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}
    </update>
 @Test
    public void testUpdateById(){
        SqlSession sqlSession=SqlSessionUtil.openSession();
        //准备数据
        Car car=new Car(7L,"9999","啊啊啊",302.1,"2000-01-01","油车");
        //执行SQL语句
        int count=sqlSession.update("updateById",car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

3.7 select 

*需求:根据id查询。

 <select id="selectById">
        select * from t_car where id=#{id}
    </select>

如果没有在select标签中添加映射的话,会报错

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'asdad.selectById'.  It's likely that neither a Result Type nor a Result Map was specified.
### The error may exist in CarMapper.xml
### The error may involve asdad.selectById
### The error occurred while handling results
### SQL: select * from t_car where id=?
### Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'asdad.selectById'.  It's likely that neither a Result Type nor a Result Map was specified.

需要特别注意的是:
select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成什么类型的java对象。你需要告诉mybatis。
resultType通常写的是:全限定类名。

下面代码中,resultType返回映射的类型

 <select id="selectById" resultType="Car">
        select * from t_car where id=#{id}
    </select>
@Test
    public  void testQueryById(){
        SqlSession sqlSession=SqlSessionUtil.openSession();
        //执行DQL语句,根据ID查询,返回结果一定是一条
        Object car=sqlSession.selectOne("selectById",1);
        System.out.println(car);
        sqlSession.close();
    }

输出结果又出现了问题

Car{id=1, carNum='null', brand='宝马520Li', guidePrice=null, produceTime='null', carType='null'}

这是为什么呢?

mysql是查询语句是根据你的字段来返回查询的结果字段

 给字段取了别名,就有返回值了

 <select id="selectById" resultType="Car">
        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
        from t_car where id=#{id}
    </select>

查询全部

 <select id="selectAll" resultType="Car">
        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
        from t_car
    </select>

 注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。selectList方法: mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。 


    @Test
    public  void testQueryAll(){
        SqlSession sqlSession=SqlSessionUtil.openSession();
        //执行DQL语句,根据ID查询,返回结果一定是一条
        List<Car> carList =sqlSession.selectList("selectAll");
        System.out.println(carList);
        sqlSession.close();
    }

3.8 namespace

XxxxMapper.xml文件中<mapper namespace="">

实际上,本质上mybatis中的sqlId的完整写法:

namespace.id

4 配置核心文件

4.1 environments 

<?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>
    <!--default表示现在使用的默认环境-->
    <environments default="development">
        <!--其中的一个环境.连接的数据库是mybatis1-->
        <!--一般一个数据库会对应一个SqlSessionFactory对象-->
        <!--一个环境environment会对应一个SqlSessionFactory对象-->
        <environment id="development">
                <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis1"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
        <!--这是mybatis的另一个环境.连接的数据库是mybatis2-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis2"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

 4.2 transactionManager

mybaits中的事务管理器

标签transactionManager:

<transactionManager type="JDBC"/>

1.作用:配置事务管理器。指定mybatis具体使用上面方式取管理事务

2.type属性有两个值:

JDBC-这个配置直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

conn.setAutoCommit(false);

...

conn.commit();

MANAGED-这个配置几乎没有做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命同期(比如JFE应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为false 来阻止默认的关闭行为。例如:

   <transactionManager type="MANAGED">
       <property name="closeConnection" value="false"/>
   </transactionManager>

如果你正在使用Spring + MyBatis,则没有必要配置事务管理器,因为Spring 模块会使用自带的管理器来覆盖前面的配置
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用TransactionFactory接口实现类的全限定名或类型别名代替它们。

3.大小写无所谓。不缺分大小写。但是不能写其他值。只能是二选一:
jdbc、 managed

4、在mybatis中提供了一个事务管理器接口:Transaction
该接口下有两个实现类:
JdbcTransaction

ManagedTransaction
如果type="JDBC",那么底层会实例化JdbcTransaction对象。如果type="MANAGED",那么底层会实例化ManakgedTransaction

4.3 dataSource

  <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis1"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>

1.dataSource被称为数据源。
2.dataSounce作用是什么?为程序提供Connection对象。(但凡是给程序提供Connection对象的,都叫做数据源。)
3.数据源实际上是一套规范。JDK中有这套规范: javax.sql.DataSource(这个数据源的规范,这套接口实际上是JDK规定的。)
4.我们自己也可以编写数据源组件,只要实现javax.sqL.DataSource接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。比如你可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源

原JDBC获取数据源的代码

//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接数据
/*
URL:统一资源定位符(网络中某个资源的绝对路径)‘
https://www.baidu.com/这就是URL。
URL包括哪几个部分?
协议:Http;//IP:127.0.0.1本机ip地址PORT:服务器地址资源名:index.html服务器上某个资源名
*/
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3、获取数据库操作对象(statement专门执行sql语句的)
stmt=conn.createStatement();

5.常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?

阿里巴巴的德鲁伊连接池:druid

c3p0

dbcp

....

6. type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:
type属性有三个值:必须是三选一。

type="[UNPOOLED | POOLED| JNDI]"

UNP00LED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。

POOLED:使用mybatis自己实现的数据库连接池。

JNDI:集成其它第三方的数据库连接池。

JNDI是一套规范。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:
例如:Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。JNDI是:java命名目录接口。Tomcat服务器实现了这个规范。

initial_context这个属性用来在initialContext 中寻找上下文(即, initalContextlookup(inital_context)。这是个可选属性,如果忽略,那么将会直接从 lIitialContext 中寻找

data_source-这是引用数据源实例位置的上下文路径。提供了initial_context配置时会在其返回的上下文中进行查找,没有提供时如直接在lnitalContext 中查找。
和其他数据源配置类似,可以通过添加前缀“env”直接把属性传递给InitialContext。比如:

env.encoding—UTF8

这就会在InitialContext 实例化时往它的构造方法传递值为UTF8的encoding属性。


<dataSource type="JNDI">
   <property name="initial_context" value="..."/>
    <property name="data_source" value="..."/>
</dataSource>

4.3.1 UNPOOLED

   <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis2"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
  @Test
    public  void testDateSource() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        //sqlSessionFactory对象:一个数据一个。不要频繁去创建该对象。
        SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));

        //通过sqlSessionFactory对象可以开启多个会话。
        //会话1
        SqlSession sqlSession1=sqlSessionFactory.openSession();
        System.out.println(sqlSession1);
        sqlSession1.close();
        //会话2
        SqlSession sqlSession2=sqlSessionFactory.openSession();
        System.out.println(sqlSession2);
        sqlSession1.close();
    }

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@524f3b3a]
==>  Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,?,?,?,?,?)
==> Parameters: 1005(String), 宝马(String), 36.6(Double), 2024-03-13(String), 油车(String)
<==    Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@524f3b3a]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@524f3b3a]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@524f3b3a]
Opening JDBC Connection
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@324a0017]
==>  Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,?,?,?,?,?)
==> Parameters: 1006(String), 宝马(String), 36.6(Double), 2024-03-13(String), 油车(String)
<==    Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@324a0017]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@324a0017]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@324a0017]

地址是不一样的

4.3.2 POOLED

  <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="333"/>
            </dataSource>
@Test
    public  void testDateSource() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        //sqlSessionFactory对象:一个数据一个。不要频繁去创建该对象。
        SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));

        //通过sqlSessionFactory对象可以开启多个会话。
        //会话1
        SqlSession sqlSession1=sqlSessionFactory.openSession();
        System.out.println(sqlSession1);
        Car car=new Car(null,"1005","宝马",36.6,"2024-03-13","油车");
        //执行SQL语句
        //insert方法的参数
        //第一个参数: sqlId,从CarMapper.xml文件中复制。
        // 第二个参数:封装数据的对象。
        sqlSession1.insert("insertCar",car);
        sqlSession1.commit();
        sqlSession1.close();
        //会话2
        SqlSession sqlSession2=sqlSessionFactory.openSession();
        car=new Car(null,"1006","宝马",36.6,"2024-03-13","油车");
        //执行SQL语句
        //insert方法的参数
        //第一个参数: sqlId,从CarMapper.xml文件中复制。
        // 第二个参数:封装数据的对象。
        sqlSession2.insert("insertCar",car);
        sqlSession2.commit();
        sqlSession2.close();
    }

Opening JDBC Connection
Created connection 1066615508.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
==>  Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,?,?,?,?,?)
==> Parameters: 1005(String), 宝马(String), 36.6(Double), 2024-03-13(String), 油车(String)
<==    Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Returned connection 1066615508 to pool.
Opening JDBC Connection
Checked out connection 1066615508 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
==>  Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,?,?,?,?,?)
==> Parameters: 1006(String), 宝马(String), 36.6(Double), 2024-03-13(String), 油车(String)
<==    Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f9342d4]
Returned connection 1066615508 to pool.

可以看到地址是一样的,地址是一样的,这把关闭不是真的关闭,是返回到连接池当中

 ​​​​​​​

 4.3.3 properties

第一种

 <!--java.util.Properties类。是一个map集合。key和value都是String类型-->
    <!--在Properties标签中可以配置很多属性-->
    <properties>
        <!--这是其中一个属性-->
        <!-- <property name="属性名" value="属性值"/>-->
        <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="333"/>
    </properties>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <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>

 第二种

 <properties url="绝对路径"></properties>

4.3.4 mapper

   <mappers>
        <!--
        执行mapper.xml文件的路径 resource属性自动会从类的根路径下开始查找资源
        -->
        <mapper resource="CarMapper.xml"/>
        <!--
      执行mapper.xml文件的路径 从指定的位置加载
      -->
        <mapper url="绝对路径"/>
    </mappers>

6 WEB中使用Mybatis

创建webapp项目

修改pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>mybatis</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>mybatis Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.14</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.31</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>mybatis</finalName>
  </build>
</project>

6.1 dao包

具体路径看package

package com.l.bank.dao;


import com.l.bank.pojo.Account;

/**
 * 账户的DA0对象。负责t_act表中数据的CRUD.
 * 强调一下:DA0对象中的任何一个方法和业务不挂钩。没有任何业务逻辑在里面。
 * DAO中的方法就是做CRUD的。所以方法名大部分是: insertXXX deleteXXX updateXXX selectXXX
 */
public interface AccountDao {
    /**
     * 根据账号查询账号信息
     * @param actno 账号
     * @return 账号信息
     */
    Account selectByActno(String actno);

    /**
     * 更新账号信息
     * @param act 被更新的账号信息
     * @return 1.表示更新成功,其他值表示更新失败
     */
    int updateByActno(Account act);

}
package com.l.bank.dao.impl;

import com.l.bank.dao.AccountDao;
import com.l.bank.pojo.Account;
import com.l.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    public Account selectByActno(String actno) {
        SqlSession sqlSession= SqlSessionUtil.openSession();
        Account account=sqlSession.selectOne("account.selectByActno",actno);
        sqlSession.close();
        return account;
    }

    public int updateByActno(Account act) {
        SqlSession sqlSession= SqlSessionUtil.openSession();
        int count =sqlSession.update("account.updateByActno",act);
        return count;
    }
}

6.2 exception包

package com.l.bank.exceptions;

/**
 * 余额不足异常
 */
public class MoneyNotEnoughException extends  Exception{
    public MoneyNotEnoughException(){

    }
    public MoneyNotEnoughException(String msg){
        super(msg);
    }
}
package com.l.bank.exceptions;

public class TransferException extends Throwable {
    public TransferException(){

    }
    public TransferException(String msg){

    }
}

6.3 pojo

package com.l.bank.pojo;

/**
 * 账号类,封装账号数据
 */
public class Account {
    private Long id;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    private String actno;

    public Account() {
    }

    private Double balance;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}

6.4 service

package com.l.bank.service;


import com.l.bank.exceptions.MoneyNotEnoughException;
import com.l.bank.exceptions.TransferException;

/**
 * 注意:业务类当中的业务方法的名字在起别名的时候,最好需要建名知意。
 * 账户业务类
 */
public interface AccountService {

    /**
     * 账号转账业务
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    void transfer(String fromActno,String toActno,double money) throws MoneyNotEnoughException, TransferException;
}
package com.l.bank.service.impl;

import com.l.bank.dao.impl.AccountDaoImpl;
import com.l.bank.pojo.Account;
import com.l.bank.service.AccountService;
import com.l.bank.dao.AccountDao;
import com.l.bank.exceptions.MoneyNotEnoughException;
import com.l.bank.exceptions.TransferException;

public class AccountServiceImpl  implements AccountService {

    private AccountDao accountDao=new AccountDaoImpl();
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
          //1.判断转出账号的余额是否充足
        Account account1=accountDao.selectByActno(fromActno);
        if(account1.getBalance()<money){
            //2.如果转出账号余额不足,提示用户
            throw  new MoneyNotEnoughException("对不起余额不足");
        }
        //3.如果转出账号余额充足,更新转出账号余额(update)
        Account account2=accountDao.selectByActno(toActno);
        account1.setBalance(account1.getBalance()-money);
        account2.setBalance(account2.getBalance()+money);
        //4.更新转入账户余额(update)
        int count=accountDao.updateByActno(account1);
        count+=accountDao.updateByActno(account2);
        if(count!=2){
          throw  new TransferException("转账异常,未知原因");
        }
    }
}

6.5 utils

package com.l.bank.utils;

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 java.io.IOException;

public class SqlSessionUtil {

    //工具类的构造方法一般都是私有化的。
    //工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
    //为了防止new对象,构造方法私有化。
    private SqlSessionUtil(){

    }

    private static SqlSessionFactory sqlSessionFactory;

    //类加载时执行
    //sqLSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象
    static{
        try {
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取会话对象
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
}

6.6 web

package com.l.bank.web;


import com.l.bank.exceptions.MoneyNotEnoughException;
import com.l.bank.exceptions.TransferException;
import com.l.bank.service.AccountService;
import com.l.bank.service.impl.AccountServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class AccountServlet extends HttpServlet {

    private AccountService accountService=new AccountServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取表单信息
        String fromActno=request.getParameter("fromActno");
        String toActno=request.getParameter("toActno");
        double money=Double.parseDouble(request.getParameter("money"));
        //调用service的转帐方法
        try {
            //调用service的转账方法完成转账。(调业务层)
            accountService.transfer(fromActno,toActno,money);
            //程序能走到这里表示一定成功
            response.sendRedirect(request.getContextPath()+"/succes.html");
        } catch (MoneyNotEnoughException e) {
            response.sendRedirect(request.getContextPath()+"/e1.html");
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath()+"/e2.html");
        }
    }
}

6.7 resources中的配置文件

AccountMapper.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="account">

    <select id="selectByActno" resultType="com.l.bank.pojo.Account">
          select  * from t_act where actno=#{actno}
    </select>

    <update id="updateByActno">
        update t_act set balance=#{balance} where actno=#{actno}
    </update>

</mapper>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置文件修改时重新加载,默认true -->
<configuration debug="false">

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8">
            <!-- 输出日志记录格式 -->
            <pattern>[%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--    mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>


    <!-- 日志输出级别,LOGBACK日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR-->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

mybatis-config.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>
        <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/yh"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="333"/>
    </properties>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
                <transactionManager type="MANAGED"/>
            <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="AccountMapper.xml"/>
    </mappers>
</configuration>

6.8 WEB-INF中的web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>com.l.bank.web.AccountServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/transfer</url-pattern>
    </servlet-mapping>
</web-app>

6.9 html页面

e1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账报告</title>
</head>
<body>
<h1>余额不足!!!</h1>
</body>
</html>

e2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账报告</title>
</head>
<body>
<h1>转账失败未知原因!!!</h1>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账报告</title>
</head>
<body>
<h1>转账失败未知原因!!!</h1>
</body>
</html>
succes.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账报告</title>
</head>
<body>
<h1>转账成功!!!</h1>
</body>
</html>

6.10 事务管理

如果按照上面的代码去执行,中间出现异常会导致数据出错。需要添加事务

修改后的SqlSessionUtil

package com.l.bank.utils;

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 java.io.IOException;

public class SqlSessionUtil {

    //工具类的构造方法一般都是私有化的。
    //工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
    //为了防止new对象,构造方法私有化。
    private SqlSessionUtil(){

    }

    private static SqlSessionFactory sqlSessionFactory;

    //类加载时执行
    //sqLSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象
    static{
        try {
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //全局的,服务器级别的,一个服务器当中定义一个即可
    //为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession
    private static ThreadLocal<SqlSession> local=new ThreadLocal<>();

    /**
     * 获取会话对象
     * @return 会话对象
     */
    public static SqlSession openSession(){
        SqlSession sqlSession=local.get();
        if(sqlSession==null){
            sqlSession=sqlSessionFactory.openSession();
            //将sqlSession对象绑定到当前线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }


    /**
     * 关闭SqlSession对象(从当前线程中移除SqlSession对象)
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession){
        if(sqlSession!=null){
            sqlSession.close();
            //注意移除SqlSession对象和当前线程的绑定关系
            //因为tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程。
            local.remove();
        }
    }
}

修改后的service.impl

package com.l.bank.service.impl;

import com.l.bank.dao.impl.AccountDaoImpl;
import com.l.bank.pojo.Account;
import com.l.bank.service.AccountService;
import com.l.bank.dao.AccountDao;
import com.l.bank.exceptions.MoneyNotEnoughException;
import com.l.bank.exceptions.TransferException;
import com.l.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl  implements AccountService {

    private AccountDao accountDao=new AccountDaoImpl();
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {

        SqlSession sqlSession= SqlSessionUtil.openSession();

          //1.判断转出账号的余额是否充足
        Account account1=accountDao.selectByActno(fromActno);
        if(account1.getBalance()<money){
            //2.如果转出账号余额不足,提示用户
            throw  new MoneyNotEnoughException("对不起余额不足");
        }
        //3.如果转出账号余额充足,更新转出账号余额(update)
        Account account2=accountDao.selectByActno(toActno);
        account1.setBalance(account1.getBalance()-money);
        account2.setBalance(account2.getBalance()+money);
        //4.更新转入账户余额(update)
        int count=accountDao.updateByActno(account1);

        //模拟异常
        String s=null;
        s.toString();

        count+=accountDao.updateByActno(account2);
        if(count!=2){
          throw  new TransferException("转账异常,未知原因");
        }
        //提交事务
        sqlSession.commit();
        //关闭事务
        SqlSessionUtil.close(sqlSession);
    }
}

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值