Java常⽤框架:
SSM三⼤框架:Spring + SpringMVC + MyBatis
SpringBoot
SpringCloud 等。。
SSM三⼤框架的学习顺序:MyBatis、Spring、SpringMVC(建议)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uxyElgS-1670084450231)(D:\mybatis笔记\三层架构.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJbt1LlO-1670084450232)(D:\mybatis笔记\ORM对象关系映射.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ptwUXgH8-1670084450233)(D:\mybatis笔记\ORM对象关系映射2.png)]
MyBatis框架特点:
⽀持定制化 SQL、存储过程、基本映射以及⾼级映射
避免了⼏乎所有的 JDBC 代码中⼿动设置参数以及获取结果集
⽀持XML开发,也⽀持注解式开发。【为了保证sql语句的灵活,所以mybatis⼤部分是采⽤
XML⽅式开发。】
将接⼝和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的
记录
体积⼩好学:两个jar包,两个XML配置⽂件。
完全做到sql解耦合。
提供了基本映射标签。
提供了⾼级映射标签。
提供了XML标签,⽀持动态SQL的编写。
准备数据库表,汽⻋表t_car,字段包括:
id:主键(⾃增)【bigint】
car_num:汽⻋编号【varchar】
brand:品牌【varchar】
guide_price:⼚家指导价【decimal类型,专⻔为财务数据准备的类型】
produce_time:⽣产时间【char,年⽉⽇即可,10个⻓度,‘2022-10-11’】
car_type:汽⻋类型(燃油⻋、电⻋、氢能源)【varchar】
先创建一个空项目,再空项目中创建一个模块
步骤1:打包⽅式:jar(不需要war,因为mybatis封装的是jdbc。)
<groupId>com.zivgroupId>
<artifactId>mybatis-002-crudartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
步骤2:引⼊依赖(mybatis依赖 + mysql驱动依赖)
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.1version>
dependency>
步骤3:在resources根⽬录下新建mybatis-config.xml配置⽂件(可以参考mybatis⼿册拷⻉)
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/ziv_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
注意1:mybatis核⼼配置⽂件的⽂件名不⼀定是mybatis-config.xml,可以是其它名字。
注意2:mybatis核⼼配置⽂件存放的位置也可以随意。这⾥选择放在resources根下,相当于放到了类的根路径下。
步骤4:在resources根⽬录下新建CarMapper.xml配置⽂件(可以参考mybatis⼿册拷⻉)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="bbb">
<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:sql语句最后结尾可以不写“;”
注意2:CarMapper.xml⽂件的名字不是固定的。可以使⽤其它名字。
注意3:CarMapper.xml⽂件的位置也是随意的。这⾥选择放在resources根下,相当于放到了类的根路径下。
注意4:将CarMapper.xml⽂件路径配置到mybatis-config.xml:
<mapper resource="CarMapper.xml"/>
步骤5:编写MyBatisIntroductionTest代码
package com.ziv.mybatis.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class MyBatisIntroductionTest {
public static void main(String[] args) {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 创建SqlSessionFactory对象
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 3. 创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 执⾏sql
int count = sqlSession.insert("insertCar"); // 这个"insertCar"必须是sql的id
System.out.println("插⼊⼏条数据:" + count);
// 5. 提交(mybatis默认采⽤的事务管理器是JDBC,默认是不提交的,需要⼿动提交。)
sqlSession.commit();
// 6. 关闭资源(只关闭是不会提交的)
sqlSession.close();
}
}
注意1:默认采⽤的事务管理器是:JDBC。JDBC事务默认是不提交的,需要⼿动提交。
步骤6:运⾏程序,查看运⾏结果,以及数据库表中的数据
经过测试说明mybatis核⼼配置⽂件的名字是随意的,存放路径也是随意的。
虽然mybatis核⼼配置⽂件的名字不是固定的,但通常该⽂件的名字叫做:mybatis-config.xml
虽然mybatis核⼼配置⽂件的路径不是固定的,但通常该⽂件会存放到类路径当中,这样让项⽬的移植更加健壮。
maven工程中的resources目录就相当于项目的根目录
在mybatis中提供了⼀个类:Resources【org.apache.ibatis.io.Resources】,该类可以从类路径当
中获取资源,我们通常使⽤它来获取输⼊流InputStream,代码如下
// 这种⽅式只能从类路径当中获取资源,也就是说mybatis-config.xml⽂件需要在类路径下
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
package com.ziv.mybatis;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisCompleteCodeTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder
.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执⾏SQL
int count = sqlSession.insert("insertCar");
System.out.println("更新了⼏条记录:" + count);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
第⼆步:编写单元测试类【测试⽤例】,测试⽤例中每⼀个测试⽅法上使⽤@Test注解进行标注
@Test
public void testDeleteById(){
}
@Test
public void testInsertCar(){
}
第三步:可以在类上执⾏,也可以在⽅法上执⾏
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
settings>
我们这里使用logback日志框架,使用第三方框架,可以不用在settings中配置
第⼀步:引⼊logback相关依赖
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
第⼆步:引⼊logback相关配置⽂件(⽂件名叫做logback.xml或logback-test.xml,放到类路径当中)
<configuration debug="false">
<property name="LOG_HOME" value="/home"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.logFileNamePattern>
<MaxHistory>30MaxHistory>
rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
encoder>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MBMaxFileSize>
triggeringPolicy>
appender>
<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"/>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
root>
configuration>
执行程序,查看打印日志:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDckUhmc-1670084450233)(D:\mybatis笔记\日志图片.png)]
package com.ziv.mybatis.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 {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
private SqlSessionUtil(){}
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
}
什么是CRUD
pojo类Car
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
...//省略getter setter等其他方法
}
<insert id="insertCar">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values (null,#{},#{},#{},#{},#{})
insert>
JDBC中使用 ? 当作占位符,mybatis中使用 #{} 当作占位符,? 和 #{} 是等效的。
mybatis可以使用map集合给sql语句中的占位符传值:#{ 这里写map集合中的key }
mybatis可以使用pojo类给sql语句中的占位符传值:#{ 这里写pojo类中的属性名 }
<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>
注意:当#{}中的属性名写错的话,会报错
org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘carNum2’ in ‘class com.ziv.mybatis.pojo.Car’
mybatis会去找:Car类中的getXxx()方法,也就是说,#{} 中写的是将Car类中getXxx()方法的get去掉得到Xxx首字母变小写得到xxx。例如:
getCarNum() --> #{carNum}
getBrand() --> #{brand}
java代码
@Test
public void testInsertCar(){
SqlSession sqlSession = SqlSessionUtil.openSession();
Car car = new Car(null,"3333","比亚迪秦",30.0,"2020-10-10","新能源");
int count = sqlSession.insert("insertCar",car);
System.out.println("count = " + count);
sqlSession.commit();
sqlSession.close();
}
<delete id="deleteById">
delete from t_car where id = #{id}
delete>
当只传一个值的时候,#{}里面的内容随意,但是不能空着。
java代码
@Test
public void testDeleteById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.delete("deleteById", 10);
System.out.println("count = " + count);
sqlSession.commit();
sqlSession.close();
}
<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(9L,"1004","凯美瑞",30.0,"1999-10-10","燃油车");
sqlSession.update("updateById",car);
sqlSession.commit();
sqlSession.close();
}
select(查一个,返回结果是一个)
<select id="selectById" resultType="com.ziv.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>
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
Car car = sqlSession.selectOne("selectById", 12);
System.out.println("car = " + car);
sqlSession.commit();
sqlSession.close();
}
mybatis执行了select语句之后,会返回一个ResultSet对象,然后从中取出数据,再封装成Java对象。所以需要通过select标签中的 resultType属性指明需要封装成什么对象。
注意:select语句中查询的列别名要和pojo类中的属性名一致
select(查所有)
<select id="selectAll" resultType="com.ziv.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>
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
List<Object> carList = sqlSession.selectList("selectAll");
System.out.println("carList = " + carList);
sqlSession.commit();
sqlSession.close();
}
在SQL Mapper配置⽂件中标签的namespace属性可以翻译为命名空间,这个命名空间主要是为了防⽌sqlId冲突的。
当有两个不同的Mapper.xml文件中的sql语句id相同的时候,就需要使用命名空间加id的形式。
@Test
public void testNamespace(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句,使用namespace.id的形式
List<Object> cars = sqlSession.selectList("car.selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
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/ziv_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ziv_mybatis2"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
@Test
public void configTest() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//build方法的第二个参数用来指定使用哪个环境配置,如果没有指定使用默认配置。
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(is).openSession();
SqlSession sqlSession2 = new SqlSessionFactoryBuilder().build(is,"production").openSession();
}
<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/ziv_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
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/ziv_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到⼀个属性资源⽂件中,假设在类的根路
径下创建jdbc.properties⽂件,配置如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ziv_mybatis2
jdbc.username=root
jdbc.password=123456
在mybatis核心配置文件中引入并使用:
<properties resource="jdbc.properties"/>
<environment id="production">
<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>
mapper标签⽤来指定SQL映射⽂件的路径,包含多种指定⽅式,这⾥先主要看其中两种:
第⼀种:resource,从类的根路径下开始加载【⽐url常⽤】
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
第⼆种:url,从指定的url位置加载
<mappers>
<mapper url="file:///d:/CarMapper.xml"/>
mappers>
mapper还有其他的指定⽅式,后⾯再看!!!
汇总
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 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/ziv_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eOhSumBB-1670084450234)(D:\mybatis笔记\bank需求分析.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOVqei1G-1670084450234)(D:\mybatis笔记\bank数据库表.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSoneTTY-1670084450234)(D:\mybatis笔记\bank数据准备.png)]
<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.0modelVersion>
<groupId>org.zivgroupId>
<artifactId>mybatis-004-webartifactId>
<version>1.0version>
<packaging>warpackaging>
<name>mybatis-004-web Maven Webappname>
<url>http://www.example.comurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.7maven.compiler.source>
<maven.compiler.target>1.7maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>jakarta.servletgroupId>
<artifactId>jakarta.servlet-apiartifactId>
<version>5.0.0version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.1version>
dependency>
dependencies>
<build>
<finalName>mybatis-004-webfinalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-pluginartifactId>
<version>3.1.0version>
plugin>
<plugin>
<artifactId>maven-resources-pluginartifactId>
<version>3.0.2version>
plugin>
<plugin>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.0version>
plugin>
<plugin>
<artifactId>maven-surefire-pluginartifactId>
<version>2.22.1version>
plugin>
<plugin>
<artifactId>maven-war-pluginartifactId>
<version>3.2.2version>
plugin>
<plugin>
<artifactId>maven-install-pluginartifactId>
<version>2.5.2version>
plugin>
<plugin>
<artifactId>maven-deploy-pluginartifactId>
<version>2.8.2version>
plugin>
plugins>
pluginManagement>
build>
project>
引⼊相关配置⽂件,放到resources⽬录下(全部放到类的根路径下)
mybatis-config.xml
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 id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="AccountMapper.xml"/>
mappers>
configuration>
AccountMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="bank">
<select id="selectByActno" resultType="com.ziv.bank.pojo.Account">
select id,actno,balance from t_act where #{actno}
select>
<update id="updateByActno">
update t_act set(balance=#{balance}) where actno = #{actno}
update>
mapper>
logback.xml
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
encoder>
appender>
<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"/>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
root>
configuration>
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ziv_mybatis2
jdbc.username=root
jdbc.password=123456
第二步 前端页面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="fromAct"><br>
转入账户:<input type="text" name="toAct"><br>
转账金额:<input type="text" name="money"><br>
<input type="submit" value="转账">
form>
body>
html>
注意:web.xml中的web-app标签属性metadata-complete=“false”,其值为false表示启用注解编程,其值为true表示不开启注解。
com.ziv.bank.pojo
com.ziv.bank.service
com.ziv.bank.service.impl
com.ziv.bank.dao
com.ziv.bank.dao.impl
com.ziv.bank.web.controller
com.ziv.bank.exception
com.ziv.bank.utils
package com.ziv.bank.pojo;
/**
* 账户类,封装数据
* @author ziv
* @version 1.0
* @since 1.0
*/
public class Account {
private Long id;
private String actno;
private Double balance;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + 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;
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
public Account() {
}
}
package com.ziv.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 {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
//ThreadLocal解决事务问题
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
private SqlSessionUtil(){}
/**
* 获取会话对象
* @return 会话对象
*/
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭sqlSession对象
*
* @param sqlSession
*/
public static void close(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
local.remove();
}
}
}
第五步 编写AccountDao接口,已经AccountDaoImpl实现类
package com.ziv.bank.dao;
import com.ziv.bank.pojo.Account;
/**
* 账户的dao对象,负责t_act的CRUD
*
* @author ziv
* @version 1.0
* @since 1.0
*/
public interface AccountDao {
/**
* 通过账号查询账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act 需要更新的账户
* @return 1 表示更新成功,其他表示更新失败
*/
int updateByActno(Account act);
}
package com.ziv.bank.dao.impl;
import com.ziv.bank.dao.AccountDao;
import com.ziv.bank.pojo.Account;
import com.ziv.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account account = (Account) sqlSession.selectOne("bank.selectByActno", actno);
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("bank.updateByActno", act);
return count;
}
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="bank">
<select id="selectByActno" resultType="com.ziv.bank.pojo.Account">
select id,actno,balance from t_act where #{actno}
select>
<update id="updateByActno">
update t_act set(balance=#{balance}) where actno = #{actno}
update>
mapper>
public class MoneyNotEnoughException extends Exception {
public MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg){super(msg);}
}
public class TransferException extends Exception {
public TransferException(){}
public TransferException(String msg){super(msg);}
}
package com.ziv.bank.service;
import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;
/**
* 处理账户业务的接口
*
* @author ziv
* @version 1.0
* @since 1.0
*/
public interface AccountService {
/**
* 账户转账业务
*
* @param fromAct 转出账户
* @param toAct 转入账户
* @param money 转账金额
*/
void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferException;
}
package com.ziv.bank.service.impl;
import com.ziv.bank.dao.AccountDao;
import com.ziv.bank.dao.impl.AccountDaoImpl;
import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;
import com.ziv.bank.pojo.Account;
import com.ziv.bank.service.AccountService;
import com.ziv.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferException {
//添加事务代码
SqlSession sqlSession = SqlSessionUtil.openSession();
//判断转出账户余额是否充足
Account fromAccount = accountDao.selectByActno(fromAct);
Account toAccount = accountDao.selectByActno(toAct);
//余额不足,抛出异常
if (fromAccount.getBalance() < money) {
throw new MoneyNotEnoughException("余额不足");
}
//余额充足,继续转账
fromAccount.setBalance(fromAccount.getBalance() - money);
toAccount.setBalance(toAccount.getBalance() + money);
int count = accountDao.updateByActno(fromAccount);
//int i = 12/0;
count += accountDao.updateByActno(toAccount);
if (count != 2) {
throw new TransferException("转账失败");
}
//提交事务
sqlSession.commit();
//关闭事务
sqlSession.close();
}
}
package com.ziv.bank.web;
import com.ziv.bank.exceptions.MoneyNotEnoughException;
import com.ziv.bank.exceptions.TransferException;
import com.ziv.bank.service.AccountService;
import com.ziv.bank.service.impl.AccountServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//@WebServlet("/transfer")
public class AccountController extends HttpServlet {
AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fromAct = request.getParameter("fromAct");
String toAct = request.getParameter("toAct");
double money = Double.parseDouble(request.getParameter("money"));
try {
accountService.transfer(fromAct, toAct, money);
response.sendRedirect(request.getContextPath()+"/success.html");
} catch (MoneyNotEnoughException e) {
e.printStackTrace();
response.sendRedirect(request.getContextPath()+"/error1.html");
} catch (TransferException e) {
response.sendRedirect(request.getContextPath()+"/error2.html");
e.printStackTrace();
}
}
}
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
我们不难发现,这个dao实现类中的⽅法代码很固定,基本上就是⼀⾏代码,通过SqlSession对象调⽤
insert、delete、update、select等⽅法,这个类中的⽅法没有任何业务逻辑,既然是这样,这个类我们
能不能动态的⽣成,以后可以不写这个类吗?答案:可以。
package com.ziv.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;
/**
* 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
*/
public class GenerateDaoProxy {
public static Object generate(SqlSession sqlSession, Class daoInterface) {
//类池
ClassPool pool = ClassPool.getDefault();
//制造类(com.ziv.bank.dao.AccountDao --> com.ziv.bank.dao.AccountDaoProxy)
CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");
//制造借口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
//实现接口
ctClass.addInterface(ctInterface);
//实现接口中的所有方法
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
//method是接口中的抽象方法
//实现method这个抽象方法
// Account selectByActno(String actno);
// public Account selectByActno(String actno){代码;}
StringBuilder methodCode = new StringBuilder();
methodCode.append("public ");
methodCode.append(method.getReturnType().getName());
methodCode.append(" ");
methodCode.append(method.getName());
methodCode.append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getName());
methodCode.append(" ");
methodCode.append("arg" + i);
if (i != parameterTypes.length - 1) {
methodCode.append(",");
}
}
methodCode.append(")");
methodCode.append("{");
//
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.ziv.bank.utils.SqlSessionUtil.openSession();");
String sqlId = daoInterface.getName() + "." + method.getName();
SqlCommandType commandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if (commandType == SqlCommandType.DELETE) {
}
if (commandType == SqlCommandType.INSERT) {
}
if (commandType == SqlCommandType.UPDATE) {
methodCode.append("return sqlSession.update(\"" + sqlId + "\", arg0);");
}
if (commandType == SqlCommandType.SELECT) {
methodCode.append("return (" + method.getReturnType().getName() + ")sqlSession.selectOne(\"" + sqlId + "\", arg0);");
}
methodCode.append("}");
try {
CtMethod ctMethod = CtMethod.make(String.valueOf(methodCode), ctClass);
ctClass.addMethod(ctMethod);
} catch (CannotCompileException e) {
e.printStackTrace();
}
});
//创建对象
Object obj = null;
try {
Class<?> clazz = ctClass.toClass();
obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
使⽤以上代码的前提是:AccountMapper.xml⽂件中的namespace必须和dao接⼝的全限定名称⼀致,id必须和dao接⼝中⽅法名⼀致。
使用${}的时机:
当需要进⾏sql语句关键字拼接的时候。必须使⽤${}
例如:通过向sql语句中注⼊asc或desc关键字,来完成数据的升序或降序排列。
拼接表名
例如:在前端传参数决定查询哪个表,t_log20221201,t_log20221202等等。
模糊查询
有三种解决方法
name like '%${name}%'
name like concat('%',#{name},'%')
name like "%"#{name}"%"
在mybatis-config.xml中指定:
<typeAliases>
<typeAlias type="com.ziv.mybatis.pojo.Car" alias="car"/>
<typeAlias type="com.ziv.mybatis.pojo.Car"/>
<package name="com.ziv.mybatis.pojo"/>
typeAliases>
在CarMapper.xml标签resultType属性中使用
<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>
<mapper resource="" class="" url=""/>
<package name="com.ziv.mybatis.mapper"/>
File->Settings->Editor->File and Code Templates
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource=""/>
<typeAliases>
<package name=""/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<package name=""/>
mappers>
configuration>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>
<insert id="insertCarUseGeneratedKey" 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>
@Test
public void testInsertCarUseGeneratedKey() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "9000", "Kaimeirui", 35.0, "2000-10-10", "燃油车");
mapper.insertCarUseGeneratedKey(car);
sqlSession.commit();
sqlSession.close();
System.out.println(car);
}
输出car之后发现id被赋上了自动生成的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQOnRj0C-1670084450235)(D:\mybatis笔记\输出结果.png)]
准备数据库表和数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAcndPdZ-1670084450235)(D:\mybatis笔记\学生表.png)]
简单类型包括:
mybatis可以自动识别简单类型,底层可以自动推断使用哪一个preparedStatement.setXxx()方法。比如传String的值,就会调用preparedStatement.setString()方法。
SQL映射文件中的标签比较完整的写法是:
<select id="selectStudentByName" resultType="student" parameterType="string">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
select>
resultType和parameterType里面本来应该填写全限定类名,但是mybatis默认提供了别名(详细查看mybatis文档),所以这里直接填写类简名。另外,sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是⽤来帮助mybatis进⾏类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强⼤的⾃动类型推断机制。
这种⽅式是⼿动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使⽤的时候通过#{map集合的key}来取值。
这⾥需要注意的是:#{} ⾥⾯写的是属性名字。这个属性名其本质上是:set/get⽅法名去掉set/get之后的名字。
对于多个参数,mybatis底层会创建⼀个map集合,以arg0/param1为key,传入的参数为 value
例如:
List<Student> students = mapper.selectByNameAndSex("张三", '⼥')
//底层封装成了这样的map集合,其中arg从0开始,param从1开始
Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
<select id="selectByNameAndSex">
select * from t_student where name = #{arg0} and sex = #{arg1}
select>
mybatis提供的key可读性太差,可以使用@Param注解自己指定key的名字
List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
<select id="selectByNameAndAge">
select * from t_student where name = #{name} and sex = #{age}
select>
注意:@Param替换的是map集合中arg0,arg1… 另外的param1,param2…还是可以继续使用的。
当查询的结果,有对应的实体类,并且查询结果只有⼀条时,可以直接用实体类接收,也可以用List集合接收。
当查询的记录条数是多条的时候,必须使⽤集合接收。如果使⽤单个实体类接收会出现异常。
当返回的数据,没有合适的实体类对应的话,可以采⽤Map集合接收。字段名做key,字段值做value。查询如果可以保证只有⼀条数据,则返回⼀个Map集合即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXI7LR0s-1670084450236)(D:\mybatis笔记\返回map.png)]
查询结果条数⼤于等于1条数据,则可以返回⼀个存储Map集合的List集合。List等同于List
拿Car的id做key,以后取出对应的Map集合时更⽅便。
/**
* 获取所有的Car,返回⼀个Map集合。
* Map集合的key是Car的id。
* Map集合的value是对应Car。
* 需要在接口上添加@MapKey注解,指定id作为map的key
* @return
*/
@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetMap();
<select id="selectAllRetMap" resultType="map">
select id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
select>
查询结果的列名和java对象的属性名对应不上怎么办?
使⽤resultMap进⾏结果映射
<resultMap id="carResultMap" type="car">
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<result property="brand" column="brand" javaType="string" jdbcType="VARCHAR"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
resultMap>
<select id="selectAllByResultMap" resultMap="carResultMap">
select * from t_car
select>
是否开启驼峰命名⾃动映射
使⽤这种⽅式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
Java命名规范:⾸字⺟⼩写,后⾯每个单词⾸字⺟⼤写,遵循驼峰命名⽅式。
SQL命名规范:全部⼩写,单词之间采⽤下划线分割。
在mybatis-config.xml⽂件中进⾏配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<select id="selectAll" resultType="long">
select count(*) from t_car
select>
@Test
public void testSelectTotal() {
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Long total = carMapper.selectTotal();
System.out.println(total);
}
<select id="selectByMultiCondition" resultType="car">
select * from t_car where 1=1
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type =#{carType}
if>
select>
where标签的作⽤:让where⼦句更加动态智能。
<select id="selectByMultiConditionWithWhere" resultType="car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type =#{carType}
if>
where>
select>
trim标签的属性:
<select id="selectByMultiConditionWithTrim" resultType="car">
select * from t_car
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" and
if>
<if test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice} and
if>
<if test="carType != null and carType != ''">
car_type =#{carType}
if>
trim>
select>
主要使⽤在update语句当中,⽤来⽣成set关键字,同时去掉最后多余的“,”
⽐如我们只更新提交的不为空的字段,如果提交的数据是null或者"",那么这个字段我们将不更新。
<update id="updateBySet">
update t_car
<set>
<if test="carNum !=null and carNum != ''">car_num=#{carNum},if>
<if test="brand !=null and brand != ''">brand=#{brand},if>
<if test="guidePrice !=null and guidePrice != ''">guide_price=#{guidePrice},if>
<if test="produceTime !=null and produceTime != ''">produce_time=#{produceTime},if>
<if test="carType !=null and carType != ''">car_type=#{carType},if>
set>
where id = #{id};
update>
<choose>
<when>when>
<when>when>
<when>when>
<otherwise>otherwise>
choose>
就等同于
if () {
} else if () {
} else if () {
} else if () {
} else {
}
有且仅有⼀个分⽀会被选择!!!!
<select id="selectByChoose" resultType="Car">
select * from t_car
<where>
<choose>
<when test="brand != null and brand != ''">
brand like "%"#{brand}"%"
when>
<when test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice}
when>
<when test="carType != null and carType != ''">
car_type = #{carType}
when>
choose>
where>
select>
循环数组或集合,动态⽣成sql,⽐如这样的SQL:
delete from t_car where id in(1,2,3);
delete from t_car where id = 1 or id = 2 or id = 3;
insert into t_car values
(null,'1001','凯美瑞',35.0,'2010-10-11','燃油⻋'),
(null,'1002','⽐亚迪唐',31.0,'2020-11-11','新能源'),
(null,'1003','⽐亚迪宋',32.0,'2020-10-11','新能源')
定义接口
int deleteByIds(@Param("ids")Long[] ids);
sql语句
<delete id="deleteByIds">
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
delete>
定义接口
int insertBatch(@Param("cars") List<Car> cars);
sql语句
<insert id="insertBatch">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
foreach>
insert>
sql标签⽤来声明sql⽚段
include标签⽤来将声明的sql⽚段包含到某个sql语句当中
作⽤:代码复⽤。易维护。
<sql id="carColumnName">
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
sql>
<select id="selectCarById" resultType="com.ziv.mybatis.pojo.Car">
select
<include refid="carColumnName"/>
from t_car
where id = #{id}
select>
准备数据库表和数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PV72KF6-1670084450236)(D:\mybatis笔记\高级映射数据表1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqE6FX55-1670084450236)(D:\mybatis笔记\高级映射数据表2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkpEPLt6-1670084450237)(D:\mybatis笔记\高级映射数据表数据.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jakb7yVy-1670084450246)(D:\mybatis笔记\高级映射数据表数据2.png)]
依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
创建相关的类和文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6WRDMO9-1670084450246)(D:\mybatis笔记\高级映射类和文件.png)]
多种⽅式,常⻅的包括三种:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Dnl6xhe-1670084450247)(D:\mybatis笔记\级联映射.png)]
pojo类Student中添加⼀个属性:Clazz clazz; 表示学⽣关联的班级对象。
编写sql语句:
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
resultMap>
<select id="selectById" resultMap="studentResultMap">
select s.sid, s.sname, c.cid, c.cname
from t_stu s left join t_clazz c on s.cid = c.cid
where s.sid = #{sid}
select>
测试程序:
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectById(1);
System.out.println(student);
sqlSession.close();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fgSNZVjM-1670084450247)(D:\mybatis笔记\image-20221203173344950.png)]
其他位置都不需要修改,只需要修改resultMap中的配置:association即可。
<resultMap id="studentResultMapAssociation" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
association>
resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
select s.sid, s.sname, c.cid, c.cname
from t_stu s left join t_clazz c on s.cid = c.cid
where s.sid = #{sid}
select>
测试结果和第一种方法一样
其他位置不需要修改,只需要修改以及添加以下三处:
第⼀处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条⼦sql语句的条件。
<resultMap id="selectResultMapByStep" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.ziv.mybatis.mapper.ClazzMapper.selectByIdStepTwo"
column="cid"/>
resultMap>
<select id="selectByIdStepOne" resultMap="selectResultMapByStep">
select sid,sname,cid from t_stu where sid = #{sid}
select>
第⼆处:在ClazzMapper接⼝中添加⽅法
package com.ziv.mybatis.mapper;
import com.ziv.mybatis.pojo.Clazz;
/**
* @author ziv
*/
public interface ClazzMapper {
/**
* 根据id查询班级的信息
* @param cid
* @return
*/
Clazz selectByIdStepTwo(Integer cid);
}
第三处:在ClazzMapper.xml⽂件中进⾏配置
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ziv.mybatis.mapper.ClazzMapper">
<select id="selectByIdStepTwo" resultType="Clazz">
select cid,cname from t_clazz where cid = #{cid}
select>
mapper>
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NtiSpKFU-1670084450248)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203191839504.png)]
分步优点:
要想⽀持延迟加载,⾮常简单,只需要在association标签中添加fetchType="lazy"即可。
修改StudentMapper.xml⽂件:
<resultMap id="selectResultMapByStep" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.ziv.mybatis.mapper.ClazzMapper.selectByIdStepTwo"
column="cid"
fetchType="lazy"/>
resultMap>
测试结果,只执行了一条sql语句
@Test
public void testSelectByIdStepOne(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdStepOne(1);
//System.out.println(student);
//只需要看学生的名字
System.out.println(student.getSname());
sqlSession.close();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHAMPOKe-1670084450248)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203192651945.png)]
在association标签中设置fetchType=“lazy”,只在当前association关联的sql语句中生效,如果需要添加全局设置,则需要在mybatis-config.xml中配置一下:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
如果开启了全局懒加载配置,但是在某个association中不希望使用懒加载,则在association中设置属性:fetchType=“eager”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aStfFQ0t-1670084450248)(D:\mybatis笔记\高级映射一对多.png)]
⼀对多的实现,通常是在⼀的⼀⽅中有List集合属性。
在Clazz类中添加List stus; 属性。
⼀对多的实现通常包括两种实现⽅式:
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
collection>
resultMap>
<select id="selectByCollection" resultMap="clazzResultMap">
select c.cid, c.cname, s.sid, s.sname
from t_clazz c left join t_stu s on c.cid = s.cid
where c.cid = #{cid}
select>
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-smMWydO6-1670084450249)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203195816354.png)]
与多对一的分步查询类似
<resultMap id="clazzResultMapByStep" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus"
select="com.ziv.mybatis.mapper.StudentMapper.selectByCidStepTwo"
column="cid"/>
resultMap>
<select id="selectByStepOne" resultMap="clazzResultMapByStep">
select cid,cname from t_clazz where cid = #{cid}
select>
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ps2Lu0Jx-1670084450249)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203201346330.png)]
与多对一相同
缓存:cache
缓存的作⽤:通过减少IO的⽅式,来提⾼程序的执⾏效率。
mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下⼀次还是这条select语句的话,直接从缓存中取,不再查数据库。⼀⽅⾯是减少了IO。另⼀⽅⾯不再执⾏繁琐的查找算法。效率⼤⼤提升。
mybatis缓存包括:
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7fj7Nvc-1670084450249)(D:\mybatis笔记\mybatis缓存.png)]
⼀级缓存默认是开启的。不需要做任何配置。
原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存。
测试
@Test
public void testSelectByCollection(){
SqlSession sqlSession = SqlSessionUtil.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz1 = mapper.selectByCollection(1001);
System.out.println(clazz1);
Clazz clazz2 = mapper.selectByCollection(1001);
System.out.println(clazz2);
sqlSession.close();
}
执行结果,只执行了一次sql语句,第二次是直接从缓存中拿到数据输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAHDYe2P-1670084450250)(C:\Users\95155\AppData\Roaming\Typora\typora-user-images\image-20221203223726884.png)]
什么情况下不会⾛缓存?
⼀级缓存失效情况包括两种:
第⼀种:第⼀次查询和第⼆次查询之间,⼿动清空了⼀级缓存。
sqlSession.clearCache();
第⼆种:第⼀次查询和第⼆次查询之间,执⾏了增删改操作。【这个增删改和哪张表没有关系,只要有insert delete update操作,⼀级缓存就失效。】
⼆级缓存的范围是SqlSessionFactory。
使⽤⼆级缓存需要具备以下⼏个条件:
<setting name="cacheEnabled" value="true">
在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置:
<cache/>
使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤。
⼆级缓存的失效:只要两次查询之间出现了增删改操作。⼆级缓存就会失效。【⼀级缓存也会失效】
⼆级缓存的相关配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8A1ddhOY-1670084450250)(D:\mybatis笔记\cache标签的属性.png)]
集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常⻅,按照以下步骤操作,就可以完成集成:
第⼀步:引⼊mybatis整合ehcache的依赖。
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.2version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
<scope>testscope>
dependency>
第⼆步:在类的根路径下新建echcache.xml⽂件,并提供以下配置信息。
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="e:/ehcache"/>
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
ehcache>
第三步:修改SqlMapper.xml⽂件中的标签,添加type属性。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
第四步:编写测试程序使⽤。
所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类等。
要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。
思考:使⽤这个插件的话,需要给这个插件配置哪些信息?
第一步:pom中添加逆向工程依赖
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.4.1version>
<configuration>
<overwrite>trueoverwrite>
configuration>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
dependencies>
plugin>
plugins>
build>
第二步:配置generatorConfig.xml
该⽂件名必须叫做:generatorConfig.xml
该⽂件必须放在类的根路径下。
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ziv_mybatis"
userId="root"
password="root">
jdbcConnection>
<javaModelGenerator targetPackage="com.ziv.mybatis.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="com.ziv.mybatis.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
sqlMapGenerator>
<javaClientGenerator
type="xmlMapper"
targetPackage="com.ziv.mybatis.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
javaClientGenerator>
<table tableName="t_car" domainObjectName="Car"/>
context>
generatorConfiguration>
第四步:运行插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnyhzyBk-1670084450250)(D:\mybatis笔记\image-20221203233414173.png)]
@Test
public void test() throws Exception {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder
().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 查⼀个
Car car = mapper.selectByPrimaryKey(89L);
System.out.println(car);
// 查所有,没有条件就是查询所有
List<Car> cars = mapper.selectByExample(null);
cars.forEach(c -> System.out.println(c));
// 多条件查询
// QBC ⻛格:Query By Criteria ⼀种查询⽅式,⽐较⾯向对象,看不到sql语句。
CarExample carExample = new CarExample();
carExample.createCriteria()
.andBrandEqualTo("丰⽥霸道")
.andGuidePriceGreaterThan(new BigDecimal(60.0));
carExample.or().andProduceTimeBetween("2000-10-11", "2022-10-11");
mapper.selectByExample(carExample);
sqlSession.commit();
}
mysql的limit后⾯两个数字:
假设已知⻚码pageNum,还有每⻚显示的记录条数pageSize,第⼀个数字可以动态的获取吗?
startIndex = (pageNum - 1) * pageSize
所以,标准通⽤的mysql分⻚SQL:
select * from tableName ...
limit (pageNum - 1) * pageSize, pageSize
CarMapper接口
List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
CarMapper.xml
<select id="selectAllByPage" resultType="Car">
select * from t_car limit #{startIndex},#{pageSize}
select>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.2version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
@Test
public void testPageHelper() throws Exception {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 开启分⻚
PageHelper.startPage(2, 2);
// 执⾏查询语句
List<Car> cars = mapper.selectAll();
// 获取分⻚信息对象
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
System.out.println(pageInfo);
/*
PageInfo{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
[Car{id=4, carNum='1003', brand='丰田霸道', guidePrice=32.00, produceTime='2001-10-11', carType='燃油车'},
Car{id=5, carNum='1003', brand='丰田霸道', guidePrice=33.00, produceTime='2002-10-11', carType='燃油车'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}
*/
}
mybatis中也提供了注解式开发⽅式,采⽤注解可以减少Sql映射⽂件的配置。
当然,使⽤注解式开发的话,sql语句是写在java程序中的,这种⽅式也会给sql语句的维护带来成本。
原则:简单sql可以注解。复杂sql使⽤xml。
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}"
int update(Car car);
@Select("select * from t_car where id = #{id}")
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "car_num", property = "carNum"),
@Result(column = "brand", property = "brand"),
@Result(column = "guide_price", property = "guidePrice"),
@Result(column = "produce_time", property = "produceTime"),
@Result(column = "car_type", property = "carType")
})
Car selectById(Long id);