定制网站SSM框架专题-MyBatis框架从零入门老杜版笔记(上)

定制网站框架从零入门老杜版笔记(上)

一、MyBatis概述

1.1 framework

1.2 MyBatis和JDBC的关系

MyBatis定制网站是增强版的jdbc,定制网站把一些功能通过反射封装好了

1.3 JDBC不足

  • sql语句写死,定制网站如果拓展功能,可能整个sql语句作废
  • set操作繁琐,如果sql定制网站语句有一百个?定制网站可能要写一百个set

1.4 了解MyBatis

  • MyBatis是对jdbc的封装
  • MyBatis之前叫做ibatis,定制网站后来才改名
  • MyBatis定制网站是持久层框架

1.5 了解ORM

  • O(Object):JVM中的java对象
  • R(Relational):定制网站关系型数据库
  • M(Mapping):映射
  • 什么是ORM?:JavaBean定制网站与数据表记录的互相映射

二、MyBatis入门程序

2.1 定制网站建数据库表


定制网站添加两条字段

2.2 加载mybatis定制网站的五个步骤

  1. 配置Maven环境,定制网站打包方式设置为jar,加载MyBatis,MySQL
<packaging>jar</packaging><dependencies>        <!--  MyBatis  -->        <dependency>            <groupId>org.mybatis</groupId>            <artifactId>mybatis</artifactId>            <version>3.5.11</version>        </dependency>        <!--  MySQL  -->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.30</version>        </dependency>    </dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 新建,编辑mybatis-config.xml文件(放入resources文件夹)
<?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/powernode"/>                <property name="username" value="xxxx"/>                <property name="password" value="xxxx"/>            </dataSource>        </environment>    </environments>    <mappers>        <mapper resource="CarMapper.xml"/>    </mappers></configuration>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  1. 新建,配置xxxMapper.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="org.mybatis.example.BlogMapper">    <insert id="">        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)        values (null,1003,"面包车",13.00,"2020-10-13","飞行汽车")    </insert></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 在xxxMapper中编写sql代码(在3中已完成)

  2. 把xxxMapper.txt定制网站文件路径放入mybatis-config.txt中(在2中已完成)

<mappers>        <mapper resource="CarMapper.xml"/></mappers>
  • 1
  • 2
  • 3

2.3 MyBatis中的事务

在mybatis-config.xml定制网站中有一行为

<transactionManager type="JDBC"/>
  • 1

type定制网站类型可以写成两种,一种是JDBC另一种是MANAGED(定制网站不区分大小写)

  • JDBC:交给JDBC处理事务(默认false,定制网站表示开启事务,定制网站需要手动提交)
  • MANAGED:有用到spring定制网站框架时设置为此,定制网站表交给框架处理事务,定制网站如果没有用到框架设置为此类型,定制网站则没有人处理事务,定制网站会自动提交

注意事项:

  • SqlSessionFactory.openSession()定制网站默认开启事务

2.4 编写MyBatis代码

   SqlSession sqlSession = null;   try {       SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();       SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));       sqlSession = sqlSessionFactory.openSession();       int count = sqlSession.insert("insertCar");       System.out.println("定制网站定制网站新增了记录"+count);       //提交事务       sqlSession.commit();   } catch (Exception e) {       if (sqlSession != null) {           sqlSession.rollback();       }       e.printStackTrace();   }finally {       if (sqlSession != null) {           sqlSession.close();       }   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.5 在MyBatis中引入JUnit

在maven中添加junit依赖

        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.13.2</version>            <scope>test</scope>        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

新建test包,以及创建CarMapperTest类

定制网站在新建的类中编写MyBatis代码

public class CarMapperTest {    @Test    public void insertCar(){        SqlSession sqlSession = null;        try {            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml"));            sqlSession = sqlSessionFactory.openSession();            int count = sqlSession.insert("insertCar");            System.out.println("新增了记录"+count);            //提交事务            sqlSession.commit();        } catch (Exception e) {            if (sqlSession != null) {                sqlSession.rollback();            }            e.printStackTrace();        }finally {            if (sqlSession != null) {                sqlSession.close();            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行,测试一下,定制网站绿色就是没问题

junit小知识点:

  • 定制网站断言测试方法:Assert.assertEquals(期望值,实际值);

2.6 MyBatis定制网站集成日志组件

  • SLF4J沙拉疯):logback定制网站就是继承沙拉疯实现的
  • LOG4J
  • LOG4J2
  • STDOUT_LOGGING:MyBatis定制网站内部实现的日志组件,

logback、log4j、log4j2定制网站是同一个作者编写的

若使用STDOUT_LOGGING,需要在mybatis-config.xml定制网站里添加配置文件
注意:settings定制网站标签必须添加在configuration下面

<configuration>    <settings>        <setting name="logImpl" value="STDOUT_LOGGING"/>    </settings></configuration>
  • 1
  • 2
  • 3
  • 4
  • 5

定制网站这边不使用STDOUT_LOGGING日志组件,定制网站我们使用最常用的logback组件
配置步骤:

  1. 在maven中添加logback的依赖
        <dependency>            <groupId>ch.qos.logback</groupId>            <artifactId>logback-classic</artifactId>            <version>1.2.11</version>            <scope>test</scope>        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 在mybatis-config.xml定制网站中删除日志设置(除了STDOUT_LOGGING定制网站自带日志组件外,定制网站其他组件不需要写日志设置)
<!-- 删除这些 -->    <settings>        <setting name="logImpl" value="STDOUT_LOGGING"/>    </settings>
  • 1
  • 2
  • 3
  • 4
  1. 写配置文件(名字必须为logback.xml或logback-test.xml)
    必须放在resources目录下
<?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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  1. 运行测试

2.7 MyBatis工具类的编写

在编写代码中,每次都通过SqlSessionFactoryBuilder三步骤获取session太麻烦,我们编写一个工具类get会比较方便点

  1. 新建类utils.SqlSessionUtil
  2. 编写代码
public class SqlSessionUtilTest {    /**     * 测试工具类     */    @Test    public void openSessionTest(){        SqlSession session = SqlSessionUtil.openSession();        int count = session.insert("insertCar");        System.out.println(count);        session.commit();                session.close();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  1. 运行测试(数据库正常添加,完成)

三、使用MyBatis完成CRUD

3.1 实现Insert新增

1.1 MyBatis中完成Map集合传参

第一个入门程序已完成,现在我们来做MyBatis的CRUD操作
入门程序有个问题:实战的时候新增数据表行数据不可能是固定值
回顾:

<mapper namespace="org.mybatis.example.BlogMapper">    <insert id="insertCar">        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)        values (null,1003,"面包车",13.00,"2020-10-13","飞行汽车")    </insert></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 继续完善这个程序,新建一个模块叫CRUD,如图,把模块1的一些文件拷贝过来,maven依赖引用
  2. 修改CarMapper.xml文件,加上占位符
    在MyBatis中占位符为#{},对应JDBC的?
    <insert id="insertCar">        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)        values (null,#{},#{},#{},#{},#{})    </insert>
  • 1
  • 2
  • 3
  • 4
  1. 编写测试代码
public class CarMapperTest {    @Test    public void insertCar(){        SqlSession session = SqlSessionUtil.openSession();        Map<String,Object> map = new HashMap<>();        map.put("carNum","1004");        map.put("brand","比亚迪汉");        map.put("guidePrice",160.00);        map.put("produceTime","2022-06-08");        map.put("carType","新能源汽车");        int count= session.insert("insertCar", map);        System.out.println("新增的条目:"+count);        session.commit();        session.close();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 修改mapper文件中的占位符#{}(大括号中间填入map.get(key)里面的key)
<mapper namespace="org.mybatis.example.BlogMapper">    <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></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 测试,完成

1.2 MyBatis中通过POJO类完成传参

  1. 新建POJO包、类(pojo.Car)
public class Car {    private Long id;    private String carNum;    private String brand;    private Double guidePrice;    private String produceTime;    private String carType;    //此处忽略构造方法、getting setting方法    .....}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  1. 编写测试代码
    @Test    public void insertCar(){        SqlSession session = SqlSessionUtil.openSession();                Car car = new Car(null,"1005","比亚迪秦",30.0,"2020-10-20","新能源");                int count= session.insert("insertCar", car);        System.out.println("新增的条目:"+count);        session.commit();        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 编辑mapper文件
<mapper namespace="org.mybatis.example.BlogMapper">    <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></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 运行测试方法,总结:
    mapper文件中填写的#{carType}),MyBatis会通过反射机制查找getCarType()方法得到值

3.3 实现Delete删除

  1. 新增Mapper删除
    <delete id="deleteById">        delete from t_car where id = #{id}    </delete>
  • 1
  • 2
  • 3
  1. 编写测试方法
    @Test    public void deleteById(){        SqlSession session = SqlSessionUtil.openSession();        session.delete("deleteById",17);        session.commit();        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 注意事项
    当参数只有一个时,参数名可以随意,但是尽量做到见名知意

3.4 实现update修改

  1. 编写mapper
    <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>
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 编写测试类
    @Test    public void testUpdateById(){        SqlSession session = SqlSessionUtil.openSession();        Car car = new Car(3L,"5123","哈哈车",1.5,"2011-01-04","新技术");        int count = session.update("updateById", car);        System.out.println(count);        session.commit();        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.5 实现select查询

1.1 selectOne查询一个

  1. 编写Mapper文件
    <select id="selectOneById" resultType="com.powernode.mybatis.pojo.Car">        select car_num as carNum,               brand,guide_price as guidePrice,               guide_price as guidePrice,               produce_time as produceTime,               car_type as carType        from t_car        where id = #{id}    </select>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 编写测试方法
    @Test    public void selectOneById(){        SqlSession session = SqlSessionUtil.openSession();        Car car = session.selectOne("selectOneById",16L);        System.out.println(car);        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意事项:

  • Mapper配置文件里要增加:resultType=“com.powernode.mybatis.pojo.Car”,指定查询出来是什么类,MyBatis会自动转换成该类
  • guide_price as guidePrice,取别名是为了让pojo类属性查询结果对应上符合ORM

1.2 selectList查询所有,返回一个集合

  1. 编写Mapper配置文件
    <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">        select car_num as carNum,               brand,guide_price as guidePrice,               guide_price as guidePrice,               produce_time as produceTime,               car_type as carType        from t_car    </select>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 编写测试代码
    @Test    public void selectAll(){        SqlSession session = SqlSessionUtil.openSession();        List<Car> cars = session.selectList("selectAll");        cars.forEach( car -> System.out.println(car));        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.6 Mapper映射文件的namespace

在mapper.xml文件中有一个namespasce,这个属性是用来指定命名空间的,用来防止id重复
在java程序中的写法:

        List<Car> cars = session.selectList("namespace.selectAll");
  • 1

这样写才是严谨、完整的

四、MyBatis核心配置文件详解

4.1 environment标签

一个environment对应一个SqlSessionFactory
一个SqlSessionFactory对应一个数据库

  1. 多环境下,配置文件这样写(两个environment)
    <environments default="development">        <!-- 一个environment对应一个数据库 -->        <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/powernode"/>                <property name="username" value="root"/>                <property name="password" value="root"/>            </dataSource>        </environment>        <environment id="development2">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://localhost:3306/powernode2"/>                <property name="username" value="root"/>                <property name="password" value="root"/>            </dataSource>        </environment>    </environments>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  1. 写测试方法
public class testConfiguration {    @Test    public void testEnvironment() throws IOException {        SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();        SqlSessionFactory sessionFactory = sessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "development2");        SqlSession session = sessionFactory.openSession();        int count = session.insert("insertCar");        System.out.println(count);        session.commit();        session.close();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4.2 transactionManager标签

在mybatis-config.xml配置文件中,有transactionManager子标签,表示设置MyBatis的事务管理器

<transactionManager type="JDBC"/>
  • 1

MyBatis中有两个事务管理器

  • JDBC:交给原生JDBC管理setAutoComit(false);需要手动comit()提交
  • MANAGED:交给JEE处理,一般由JEE框架处理,如Spring

MyBatis底层有一个Transaction接口,实现两个事务

  • 如果type=“JDBC”,那么底层会实例化JdbcTransaciton对象
  • 如果type=“MANAGED”,那么底层会实例化ManagedTransaciton

4.3 dataSource标签

<dataSource type="POOLED">	.......</dataSource>
  • 1
  • 2
  • 3

问:这个标签有啥用
答:dataSource表示数据源,用来获取Connection对象

它的type属性可以填写三个值:

  • UNPOOLED:不使用数据库连接池,每次获取Connection都要创建一个新对象
  • POOLED:使用MyBatis自带的连接池
  • JNDI:连接第三方的数据源(如果自己手写框架也可以用这个)

3.1 type属性详解

不同类型的数据源下有不同的属性,比如

<dataSource type="POOLED">	....	<properties/>	....</dataSource>
  • 1
  • 2
  • 3
  • 4
  • 5

<dataSource type="JNDI">	....	<properties/>	....</dataSource>
  • 1
  • 2
  • 3
  • 4
  • 5

<properties/>
  • 1

数量、内容是不一样的,具体需要看官方文档
JDNI是:java命名目录接口,Tomcat服务器实现了这个规范
假如需要用第三方数据源,Tomcat中实现了这个数据源,则在properties中填写对应的数据,要和tomat对应上

3.2 pooled和unpooled的区别

  1. unpooled表示不使用连接池,每次请求过来都会创建一个Connection
  2. pooled表示使用MyBatis自带的连接池:请求过来会先从连接池获取Connection对象

:使用连接池有什么好处?

3. 迟内的Connection数量是固定的,比如池子大小是5,如果5个连接都被占用,第6个要获取连接就先等待,数量固定
4. 假如有人一直F5刷新,没有用连接池的话,就会一直创建Connection对象,如果实例化对象过多,可能会导致服务器宕机,数量固定
5. 有新请求,第一反应去池中查找,可以增加效率

3.3 配置具体的数据库连接池参数

<!--连接池最大连接数,默认:10--><property name="poolMaximumActiveConnections " value="10"/><!--可以同时存在的最大空闲连接数,空闲太多则真正关闭一些Connection--><property name="poolMaximumIdleConnections " value="5"/><!--超时强制关闭时间,默认20000--><property name="poolMaximumCheckoutTime " value="20000"/><!--如果连接花费时间很长,连接池会隔断时间尝试重新连接并打印日志--><property name="poolTimeToWait " value="2000"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.4 properties标签

1. 第一种写法(写在配置文件内)

在configuration标签下有一个properties子标签,是用来设置变量的

<property name="key" value="value"/>
  • 1

这样写相当于往map集合里放入一个key,可以通过key拿到value,在mybatis-config.xml的其他地方,可以写成${key}获取到value

2. 第二种写法(外部文件-相对路径)

<properties resource="jdbc.properties"/>
  • 1

然后在外部新建一个jdbc.properties,如(等号左边是key右边是value)

driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/powernodeusername=rootpassword=root
  • 1
  • 2
  • 3
  • 4

3. 第三种写法(外部文件-绝对路径)

不推荐这种写法

<properties url="file:///D://jdbc.properties"/>
  • 1

五、在Web中应用MyBatis(使用MVC架构模式)

5.1 环境搭建

建立如下数据库:

5.2 idea环境搭建

1. idea新建一个maven项目,Create from archetype勾选上,选择如图所示webapp

2. maven依赖配置好

 <dependencies>    <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>8.0.30</version>    </dependency>    <dependency>      <groupId>servletapi</groupId>      <artifactId>servletapi</artifactId>      <version>2.4-20040521</version>    </dependency>    <dependency>      <groupId>ch.qos.logback</groupId>      <artifactId>logback-classic</artifactId>      <version>1.2.11</version>    </dependency>    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis</artifactId>      <version>3.5.11</version>    </dependency>    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.11</version>      <scope>test</scope>    </dependency>  </dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3. 修改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_3_1.xsd"         version="3.1"         metadata-complete="true"></web-app>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4. 编写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 resource="jdbc.properties"/>    <environments default="development">        <!-- 一个environment对应一个数据库 -->        <environment id="development">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="${driver}"/>                <property name="url" value="${url}"/>                <property name="username" value="${username}"/>                <property name="password" value="${password}"/>            </dataSource>        </environment>        <environment id="development2">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://localhost:3306/powernode2"/>                <property name="username" value="root"/>                <property name="password" value="root"/>            </dataSource>        </environment>    </environments>    <mappers>        <mapper resource="ActMapper.xml"/>    </mappers></configuration>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

5. 在resources目录下新建、编辑jdbc.properties文件

driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/powernodeusername=rootpassword=root
  • 1
  • 2
  • 3
  • 4

6. 在resources目录下新建、编写Mapper文件

<?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">    <insert id="insertAct">        insert into t_act(id,name,balance)        values (null,#{name},#{balance})    </insert></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7. 在resources目录下新建、编写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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

8. 新建pojo类,放到com.powernode.bank.pojo包中

public class Account {    private Long id;    private String name;    private Double balance;......//此处省略构造方法、getting setting toString方法,请自行创建
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

9. 新建dao, servce, utils, web,并在utils中新建SqlSessionUtil类

package com.powpernode.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;/** * @author huochai * @date 2022/10/15 9:22 */public class SqlSessionUtil {    /**     * 一个sessionFactory对应一个environment(数据库),所以不要每次运行都new一个新的     */    /**     * 构造方法设置为私有的,防止被实例化     */    private SqlSessionUtil(){}    private static SqlSessionFactory sessionFactory;    static {        try {            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));        } catch (IOException e) {            e.printStackTrace();        }    }    public static SqlSession openSession(){        SqlSession session = sessionFactory.openSession();        return session;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

10. 在webapp中新建index.html

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>银行转账</title></head><body><form action="/bank/transfer" method="post">  转出账户:<input type="text" name="fromActno"><br>  转入账户:<input type="text" name="toActno"><br>  转账金额<input type="text" name="money"><br>  <input type="submit" value="转账"></form></body></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5.3 后端代码实现

根据前端请求发送的路线完善后端代码

1. 新建、完善AccountServlet(接口与实现类)

package com.powpernode.bank.web;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;import com.powpernode.bank.service.AccountService;import com.powpernode.bank.service.impl.AccountServiceImpl;import javax.jws.WebService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author huochai * @date 2022/10/16 15:15 */@WebServlet("/transfer")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"));        //调用业务类处理转账        try {            accountService.transfer(fromActno,toActno,money);            response.sendRedirect(request.getContextPath()+"/success.html");        } catch (MoneyNotEnoughException e) {            e.printStackTrace();            response.sendRedirect(request.getContextPath()+"/error1.html");        } catch (TransferNotSuccessException e) {            e.printStackTrace();            response.sendRedirect(request.getContextPath()+"/error2.html");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

2. 完善业务层AccountService接口、实现类

package com.powpernode.bank.service;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;/** * @author huochai * @date 2022/10/16 15:17 */public interface AccountService {    /**     * 转账业务方法     * @param fromAct 转出账户     * @param toAct 转入账户     * @param money 转账金额     * @throws MoneyNotEnoughException 转出账户余额不足异常     * @throws TransferNotSuccessException 转账失败异常     */    void transfer(String fromAct,String toAct,Double money) throws MoneyNotEnoughException, TransferNotSuccessException;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
package com.powpernode.bank.service.impl;import com.powpernode.bank.dao.AccountDao;import com.powpernode.bank.dao.impl.AccountDaoImpl;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;import com.powpernode.bank.pojo.Account;import com.powpernode.bank.service.AccountService;import javax.jws.WebService;/** * @author huochai * @date 2022/10/16 15:19 * 只负责处理业务逻辑,不涉及数据库修改 */public class AccountServiceImpl implements AccountService {    private AccountDao accountDao = new AccountDaoImpl();    @Override    public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferNotSuccessException {        //判断余额是否充足        Account fromAccount = accountDao.selectById(Long.parseLong(fromAct));        if (fromAccount.getBalance()<money) {            throw new MoneyNotEnoughException("对不起,余额不足");        }        //将fromAct减少money,toAct增加money        Account toAccount = accountDao.selectById(Long.parseLong(toAct));        fromAccount.setBalance(fromAccount.getBalance()-money);        toAccount.setBalance(toAccount.getBalance()+money);        //更新两个账户        int count = accountDao.updateAccount(fromAccount);        count += accountDao.updateAccount(toAccount);                if (count<2){            throw new TransferNotSuccessException("转账失败,未知错误");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

3. 完善DAO层,接口、实现类

package com.powpernode.bank.dao;import com.powpernode.bank.pojo.Account;/** * @author huochai * @date 2022/10/16 15:20 */public interface AccountDao {    /**     * 根据ID查询账户     * @param id id     * @return 返回账户     */    Account selectById(Long id);    /**     * 更新用户     * @param account 需要更新的用户     * @return 1表示更新完成     */    int updateAccount(Account account);    /**     * 插入用户     * @param account 需要插入的用户     * @return 1表示插入完成     */    int insertAccount(Account account);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
package com.powpernode.bank.dao.impl;import com.powpernode.bank.dao.AccountDao;import com.powpernode.bank.pojo.Account;import com.powpernode.bank.utils.SqlSessionUtil;import org.apache.ibatis.session.SqlSession;/** * @author huochai * @date 2022/10/16 15:21 */public class AccountDaoImpl implements AccountDao {    @Override    public Account selectById(Long id) {        SqlSession session = SqlSessionUtil.openSession();        Account account = session.selectOne("account.selectById", id);        session.close();        return account;    }    @Override    public int updateAccount(Account account) {        SqlSession session = SqlSessionUtil.openSession();        int count = session.update("account.updateById", account);        session.commit();        return count;    }    @Override    public int insertAccount(Account account) {        SqlSession session = SqlSessionUtil.openSession();        int count = session.insert("account.insertAct", account);        session.commit();        return count;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4. 完善两个异常类

/** * @author huochai * @date 2022/10/16 15:47 */public class MoneyNotEnoughException extends Exception{    public MoneyNotEnoughException(){};    public MoneyNotEnoughException(String msg){        super(msg);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
package com.powpernode.bank.exception;/** * @author huochai * @date 2022/10/16 15:51 */public class TransferNotSuccessException extends Exception{    public TransferNotSuccessException(){};    public TransferNotSuccessException(String msg){        super(msg);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5. 当前项目结构

6. 完善事务处理

目前为止项目里存在一个问题,没有事务处理机制
如果在更新完账户1之后异常,就会出现少钱的现象

6.1 加入线程池

/*** 完善后的SqlSessionUtil工具类*/package com.powpernode.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;/** * @author huochai * @date 2022/10/15 9:22 */public class SqlSessionUtil {    public static ThreadLocal<SqlSession> local = new ThreadLocal();    private SqlSessionUtil(){}    private static SqlSessionFactory sessionFactory;    static {        try {            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));        } catch (IOException e) {            e.printStackTrace();        }    }    public static SqlSession openSession(){        SqlSession session = local.get();        if (session==null) {            session = sessionFactory.openSession();            local.set(session);        }        return session;    }    public static void close(SqlSession session){        if (session!=null) {            session.close();            local.remove();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

6.2 在业务层控制事务处理

public class AccountServiceImpl implements AccountService {    private AccountDao accountDao = new AccountDaoImpl();    @Override    public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferNotSuccessException {        SqlSession session = SqlSessionUtil.openSession();        //判断余额是否充足        Account fromAccount = accountDao.selectById(Long.parseLong(fromAct));        if (fromAccount.getBalance()<money) {            throw new MoneyNotEnoughException("对不起,余额不足");        }        //将fromAct减少money,toAct增加money        Account toAccount = accountDao.selectById(Long.parseLong(toAct));        fromAccount.setBalance(fromAccount.getBalance()-money);        toAccount.setBalance(toAccount.getBalance()+money);        //更新两个账户        int count = accountDao.updateAccount(fromAccount);        count += accountDao.updateAccount(toAccount);        if (count<2){            throw new TransferNotSuccessException("转账失败,未知错误");        }        session.commit();        SqlSessionUtil.close(session);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

5.4 MyBatis作用域(Scope)和生命周期

名称生命周期
SqlSessionFactoryBuilder只是用来buildFactory的,利用完即可丢弃
SqlSessionFactory一个数据库对应一个Factory,最好不要丢弃
SqlSession一个线程对应一个SqlSession

观察项目还有什么问题,发现DAO层代码很少并且很固定

public class AccountDaoImpl implements AccountDao {//可以发现第一行都是获取Session,第二行执行,第三行return    @Override    public Account selectById(Long id) {        SqlSession session = SqlSessionUtil.openSession();        Account account = session.selectOne("account.selectById", id);        return account;    }    @Override    public int updateAccount(Account account) {        SqlSession session = SqlSessionUtil.openSession();        int count = session.update("account.updateById", account);        return count;    }    @Override    public int insertAccount(Account account) {        SqlSession session = SqlSessionUtil.openSession();        int count = session.insert("account.insertAct", account);        return count;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

有没有什么框架可以代替这种简单重复的工作呢?
答:可以利用javassist框架
通过使用Javassist对字节码操作为JBoss实现动态“AOP”框架

5.5 使用javassist动态生成类

public class JavassistTest{    @Test    public void generateClass() throws Exception {        //获取类池        ClassPool classPool = ClassPool.getDefault();        // 制造类        CtClass ctClass = classPool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");        // 制造方法        String method = "public void insert(){System.out.println(166);}";        CtMethod make = CtMethod.make(method, ctClass);        // 把方法加到类中        ctClass.addMethod(make);        //在内存中生成类        ctClass.toClass();        Class<?> aClass = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl");        Object o = aClass.newInstance();        Method insert = aClass.getDeclaredMethod("insert");                insert.invoke(o);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5.6 使用javassist生成类并实现接口

    @Test    public void generateInterface() throws Exception{        //获取类池        ClassPool pool = ClassPool.getDefault();        //制造类        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");        //制造接口        CtClass ctClass1 = pool.makeInterface("com.powernode.javassist.test.AccountDao");        //添加接口        ctClass.addInterface(ctClass1);        CtMethod make = CtMethod.make("public void delete(){System.out.println(\"Hello delete\");}", ctClass);        ctClass.addMethod(make);        //装载类        Class<?> aClass = ctClass.toClass();        AccountDao o = (AccountDao) aClass.newInstance();        o.delete();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

5.7 使用javassist动态实现接口中所有方法

	@Test    public void generateInterfaceAll() throws Exception{        //获取类池        ClassPool pool = ClassPool.getDefault();        //制造类        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");        //制造接口        CtClass ctInterface = pool.makeInterface("com.powernode.javassist.test.AccountDao");        //类实现接口        ctClass.addInterface(ctInterface);        Method[] declaredMethods = AccountDao.class.getDeclaredMethods();        //制造方法        //方法内容        Arrays.stream(declaredMethods).forEach(method -> {            try {                StringBuffer methodValue = new StringBuffer();                methodValue.append("public ");                methodValue.append(method.getReturnType().getName()+" ");                methodValue.append(method.getName()+"(");                Class<?>[] parameterTypes = method.getParameterTypes();                for (int i = 0; i < parameterTypes.length; i++) {                    methodValue.append(parameterTypes[i].getName()+" ");                    methodValue.append("args"+i);                    if (i!= parameterTypes.length-1){                        methodValue.append(",");                    }                }                methodValue.append(")");                methodValue.append("{System.out.println(\"hello all\");");                if ("void".equals(method.getReturnType().getName())){                }else if ("int".equals(method.getReturnType().getName())){                    methodValue.append("return 1;");                }else if("java.lang.String".equals(method.getReturnType().getName())){                    methodValue.append("return \"666\";");                }                methodValue.append("}");                CtMethod make = CtMethod.make(String.valueOf(methodValue), ctClass);                ctClass.addMethod(make);            } catch (CannotCompileException e) {                e.printStackTrace();            }        });        Class<?> toClass = ctClass.toClass();        AccountDao accountDao = (AccountDao) toClass.newInstance();        accountDao.delete();        accountDao.insert("666",50.00);        accountDao.selectByActno("555");    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

5.8 编写工具类GenerateDaoProxy(自动实现DAO层接口)

注意:若想利用GenerateDaoProxy,
则Mapper.xml文件的namespace必须为DAO层接口的全类名
sqlId必须为DAO层接口中的方法

<?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.powpernode.bank.dao.AccountDao">    <insert id="insertAccount">        insert into t_act(id,name,balance)        values (null,#{name},#{balance})    </insert>    <select id="selectById" resultType="com.powpernode.bank.pojo.Account">        select * from t_act where id=#{id}    </select>    <update id="updateAccount">        update t_act set name=#{name},balance=#{balance} where id=#{id}    </update></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
package com.powpernode.bank.utils;import org.apache.ibatis.javassist.CannotCompileException;import org.apache.ibatis.javassist.ClassPool;import org.apache.ibatis.javassist.CtClass;import org.apache.ibatis.javassist.CtMethod;import org.apache.ibatis.mapping.SqlCommandType;import org.apache.ibatis.session.SqlSession;import java.lang.reflect.Method;import java.util.Arrays;/** * @author huochai * @date 2022/10/16 23:30 */public class GenerateDaoProxy {    /**     * 这个工具类是框架的开发者提供的     * 开发者可以给使用者规定传进哪些参数     *     * 传进接口,返回实现所有方法的类     * @param daoInterface 接口     * @return Impl类     */    public static Object generate(SqlSession session, Class daoInterface)  {        //类池        ClassPool pool = ClassPool.getDefault();        //制造类        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");        //制造接口        CtClass ctInterface = pool.makeInterface(daoInterface.getName());        ctClass.addInterface(ctInterface);        Method[] declaredMethods = daoInterface.getDeclaredMethods();        Arrays.stream(declaredMethods).forEach(method -> {            try {                StringBuffer methodCode = new StringBuffer();                //添加修饰符                methodCode.append("public ");                //添加返回值                methodCode.append(method.getReturnType().getName()+" ");                methodCode.append(method.getName());                methodCode.append("(");                Class<?>[] parameterTypes = method.getParameterTypes();                for (int i = 0; i < parameterTypes.length; i++) {                    methodCode.append(parameterTypes[i].getName()+" ");                    methodCode.append("arg"+i);                    if (i!= parameterTypes.length-1){                        methodCode.append(",");                    }                }                methodCode.append("){");                /**                 * 括号中间需要写对应的session.insert或session.select方法                 */                String sqlId = daoInterface.getName()+"."+method.getName();                SqlCommandType sqlCommandType = session.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();                methodCode.append("org.apache.ibatis.session.SqlSession session = com.powpernode.bank.utils.SqlSessionUtil.openSession();");                if(sqlCommandType == SqlCommandType.INSERT){                }                if(sqlCommandType == SqlCommandType.DELETE){                }                if(sqlCommandType == SqlCommandType.UPDATE){                    methodCode.append("return session.update(\""+sqlId+"\", arg0);");                }                if(sqlCommandType == SqlCommandType.SELECT){                    String resultType = method.getReturnType().getName();                    methodCode.append("return ("+resultType+")session.selectOne(\""+sqlId+"\", arg0);");                }                methodCode.append("}");                System.out.println(methodCode.toString());                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);                ctClass.addMethod(ctMethod);            } catch (CannotCompileException e) {                e.printStackTrace();            }        });        Object obj = null;        try {            Class<?> toClass = ctClass.toClass();            obj = toClass.newInstance();        } catch (Exception e) {            e.printStackTrace();        }        return obj;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

最后在业务层引用Dao的时候改一下即可:

public class AccountServiceImpl implements AccountService {    private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);.....
  • 1
  • 2
  • 3

写完以后得知一个好消息,MyBatis已经实现了映射机制,不用自己手写代码了(要求和上面一样,对namespace以及sqlId有格式要求)

//自己写的private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);//MyBatis自带private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
  • 1
  • 2
  • 3
  • 4
  • 5

5.9 面向接口的方式进行CRUD

新建maven模块mybatis-005-crud2
添加maven依赖

<?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">    <parent>        <artifactId>MyBatis</artifactId>        <groupId>org.example</groupId>        <version>1.0-SNAPSHOT</version>    </parent>    <modelVersion>4.0.0</modelVersion>    <packaging>jar</packaging>    <dependencies>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.30</version>        </dependency>        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>javax.servlet-api</artifactId>            <version>4.0.1</version>        </dependency>        <dependency>            <groupId>ch.qos.logback</groupId>            <artifactId>logback-classic</artifactId>            <version>1.2.11</version>        </dependency>        <dependency>            <groupId>org.mybatis</groupId>            <artifactId>mybatis</artifactId>            <version>3.4.6</version>        </dependency>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.11</version>            <scope>test</scope>        </dependency>    </dependencies>    <groupId>com.powernode</groupId>    <artifactId>mybatis-005-crud2</artifactId>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>    </properties></project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

添加mapper类

/** * @author huochai * @date 2022/10/17 9:38 * 在MyBatis中,一般不叫XXXDao,一般叫xxxMapper */public interface CarMapper {    /**     * 插入数据     * @param car     * @return 返回1表示正常     */    int insert(Car car);    /**     * 根据ID删除     * @param id     * @return 返回1表示正常     */    int deleteById(Long id);    /**     * 更新数据     * @param car     * @return 返回1表示正常     */    int update(Car car);    /**     * 根据ID查询     * @param id     * @return 返回1表示正常     */    Car selectById(Long id);    /**     * 查询所有的数据     * @return 返回给List集合     */    List<Car> selectAll();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

编写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="com.powernode.mybatis.mapper.CarMapper">    <insert id="insert">      insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)      values(null, #{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})    </insert>    <delete id="delete">      delete from t_car where id=#{id}    </delete>    <update id="update">      update t_car      set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType}      where id=#{id}    </update>    <select id="selectById" resultType="com.powernode.mybatis.pojo.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="com.powernode.mybatis.pojo.Car">        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType        from t_car    </select></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

新建工具类,SqlSessionUtil

public class SqlSessionUtil {        public static ThreadLocal<SqlSession> local = new ThreadLocal();    private SqlSessionUtil(){}    private static SqlSessionFactory sessionFactory;    static {        try {            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));        } catch (IOException e) {            e.printStackTrace();        }    }    public static SqlSession openSession(){        SqlSession session = local.get();        if (session==null) {            session = sessionFactory.openSession();            local.set(session);        }        return session;    }    public static void close(SqlSession session){        if (session!=null) {            session.close();            local.remove();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

编写测试用例

public class TestMapper {    @Test    public void insert(){        SqlSession session = SqlSessionUtil.openSession();        CarMapper mapper = session.getMapper(CarMapper.class);        Car car = new Car(null,"5556","兰博基尼六",1952.1,"2060-02-06","跑车");        mapper.insert(car);        session.commit();    }    @Test    public void delete(){        SqlSession session = SqlSessionUtil.openSession();        CarMapper mapper = session.getMapper(CarMapper.class);        mapper.deleteById(21L);        session.commit();    }    @Test    public void update(){        SqlSession session = SqlSessionUtil.openSession();        CarMapper mapper = session.getMapper(CarMapper.class);        Car car = new Car(18L,"5556","兰博基尼六",1952.1,"2060-02-06","跑车");        mapper.update(car);        session.commit();    }    @Test    public void selectById(){        SqlSession session = SqlSessionUtil.openSession();        CarMapper mapper = session.getMapper(CarMapper.class);        Car car = mapper.selectById(18L);        System.out.println(car);    }    @Test    public void selectAll(){        SqlSession session = SqlSessionUtil.openSession();        CarMapper mapper = session.getMapper(CarMapper.class);        List<Car> cars = mapper.selectAll();        cars.forEach(car -> System.out.println(car));    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

六、MyBatis小技巧

6.1 #{}和${}的区别

  • #{property}:底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符?传值。可以避免SQL注入的风险
  • ${property}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险

6.2 什么时候使用${}

  1. 如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。
    例:
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">    <select id="selectByType" resultType="com.powernode.mybatis.pojo.Car">        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType        from t_car        order by produce_time ${ascOrDesc}    </select></mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
public class CarMapperTest {    @Test    public void selectAllSortedTest(){        SqlSession session = SqlSessionUtil.openSession();        List<Car> cars = session.getMapper(CarMapper.class).selectByType("desc");        cars.forEach( car -> System.out.println(car));        session.close();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 拼接表名的时候,比如需要记录日志信息,如果每天都往同一个日志表中存储数据,慢慢的表信息就会越来越多,可以采用加日期分类的方法,直接查询天表可以增加效率,如t_log_20221017这样分类(然后拼接表名就可以采用${}的方法)
  2. 批量删除,可以写,删除用户123、456、8/7
delete from t_car where id in (123,456,789)//用美元括号delete from t_car where id in (${})
  • 1
  • 2
  • 3

6.3 查找包含某个关键词str的方法

  1. concat()函数
    <select id="selectLikeSome" resultType="com.powernode.mybatis.pojo.Car">        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType        from t_car        where car_type like Concat('%',#{str},'%')    </select>
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 用双引号把通配符引出去,让#{}在外面好被jdbc检测到(常用)
    <select id="selectLikeSome" resultType="com.powernode.mybatis.pojo.Car">        select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType        from t_car        where car_type like "%"#{str}"%"    </select>
  • 1
  • 2
  • 3
  • 4
  • 5

6.4 MyBatis中起别名

namespace不能起别名
所有别名不区分大小写

    <typeAliases>        <!--给type起别名,可以直接用alias名称读取到-->        <typeAlias type="com.powernode.mybatis.pojo.Car" alias="car"/>        <!--省略alias,默认就是类简名,比如car-->        <typeAlias type="com.powernode.mybatis.pojo.Car"/>        <!--包下所有类自动起别名,不区分大小写-->        <package name="com.powernode.mybatis.pojo"/>    </typeAliases>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

6.5 MyBatis小技巧之Mapper的配置

    <mappers>        <!--1. 根路径下查找CarMapper.xml文件-->        <mapper resource="CarMapper.xml"/>        <!--2. 绝对路径查找-->        <mapper url="file://c:/CarMapper.xml"/>        <!--3. 查找映射接口同级目录下的Mapper.xml文件-->        <!--   com/powernode/mybatis/mapper 建包需要这样建-->        <mapper class="com.powernode.mybatis.mapper.CarMapper"/>        <!--最常用:路径下自动查找接口对应名字xml文件-->        <package name="com.powernode.mybatis.mapper"/>    </mappers>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

6.6 插入数据时获取自动生成的主键

插入一条数据的时候,自动返回主键到制定属性中

useGeneratedKeys=“true” 表示使用自动生成的主键值
keyProperty=“id” 制定属性值赋值给对象的哪个属性

    <insert id="insertCar" useGeneratedKeys="true" keyProperty="id">        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)        values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})    </insert>
  • 1
  • 2
  • 3
  • 4
    @Test    public void insertCar(){        SqlSession session = SqlSessionUtil.openSession();        Car car = new Car(null,"9851","比亚迪ao",30.0,"2020-10-20","新能源");        session.getMapper(CarMapper.class).insertCar(car);        System.out.println(car);        session.close();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果:Car{id=22, carNum=‘9851’, brand=‘比亚迪ao’, guidePrice=30.0, produceTime=‘2020-10-20’, carType=‘新能源’}
把自动递增的主键返回给了id

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发