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);
}
}