第一步:设置打包方式为:jar包。(因为MyBatis封装的是JDBC,JDBC不需要打成war包,我们在学习JDBC的时候就不需要部署到web服务器中,所以直接打包成jar包就可以)
<packaging>jarpackaging>
第二步:引入依赖(MyBatis依赖 + MySQL驱动依赖)
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.20version>
dependency>
dependencies>
第三步:在resources根目录下新建MyBatis的核心配置文件,核心配置文件大家都起名叫做“mybatis-config.xml”(可以参考mybatis手册:https://mybatis.org/mybatis-3/zh/index.html), mybatis-config.xml文件的内容如下:[从xml中构建SqlSessionFactory,SqlSessionFactory的构建需要这个xml文件,这个xml文件就是MyBatis的核心配置文件]
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapping.xml"/>
mappers>
configuration>
MyBatis的核心配置文件的文件名不一定是mybatis-config.xml,可以是其他的。
解决数据库乱码问题,可以在url后面拼接上: ?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
SqlSessionFactory是通过核心配置xml文件构建的,一个数据库对应一个SqlSessionFactory对象,如果需要使用多个数据库,就需要使用多个environment标签,在获取SqlSessionFactory对象的时候,需要指定environment标签的id属性值,来构建属于哪个数据库的SqlSessionFactory对象。
MyBatis核心配置文件的存放的位置可以是随意的。
如果写道其他位置再获取SqlSessionFactory对象的时候需要传入一个MyBatis的核心配置文件的输入流对象,在获取输入流的时候只需要将路径指向其他位置即可,但是为了移植性更好,一般没有这么做的,我们只需要知道能这么用即可。
如果写在类的根路径下我们再获取核心配置文件的输入流的时候就更加容易了,可以直接通过:
// Resources.getResourceAsStream方法就刚好是从类的根路径下开始查找的,所以我们一般都写再类根路径下
Resources.getResourceAsStream("mybatis-config.xml");
第四步:在resources根目录下新建SQL映射文件,在这里以CarMapping.xml为例(可以参考MyBatis手册【从参考手册拷贝下来的时候https改成http这样SQL语句就不是纯灰色了】),CarMapping.xml文件的内容如下所示:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="asdbbdsa">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_type)
values('1003','雅阁',30.0,'2020-10-11','燃油车')
insert>
mapper>
编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
package com.powernode;
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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Main2 {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 获取MyBatis核心配置文件的输入流以下这四种方式都可以
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//InputStream is = ClassLoader.getSystemResourceAsStream("mybatis-config.xml");
//InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
//InputStream is = new FileInputStream("D:\\JavaCode\\mybatis\\mubatis-001-introduction\\src\\main\\resources\\mybatis-config.xml");
// 创建SqlSessionFactory对象,一般是一个数据库一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 创建SqlSession对象,专门用来执行sql语句的对象
sqlSession = sqlSessionFactory.openSession();
// 返回值是影响的数据库表中的记录的条数
int count = sqlSession.insert("insertCar");
System.out.println("成功影响了:" + count + "条数据!");
// MyBatis获取的SqlSession对象不支持自动提交,需要手动提交以下才能持久化保存到数据库中
sqlSession.commit();
} catch (IOException e) {
/*遇到异常回滚事务*/
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
if (sqlSession != null) {
/*关闭资源*/
sqlSession.close();
}
}
}
}
MyBatis中有两个主要的配置文件:
关于第一个程序的小细节
在Maven规范目录中:
src
main [主程序]
java [类的根目录]
resources [类的根目录]
test [测试程序]
java [类的根目录]
resources [类的根目录]
resources目录
看似main和test下的java没有在同一个文件下下,但是都是类的根目录,都是相通的。
使用lambda(拉姆达)表达式遍历集合:
// 拉姆达表达式只能遍历集合,不能遍历数组
List<String> list = new ArrayList<>();
list.add("123");
list.add("456");
list.add("789");
// l1:表示集合中的一项内容,这个只是一个标识符,自定义的
list.forEach(l1-> {System.out.println(l1);});/*....如果有多个Java语句,这里使用大括号括起来,如果只有一个Java语句大括号可以省略*/
Map<String,Object> map = new HashMap<>();
map.put("111","111");
map.put("222","222");
// Map集合中有两个参数:多个参数之间使用()括起来,一个参数可以省略小括号
// l1和l2分别代表集合的key和value
map.forEach((l1,l2) -> System.out.println(l1 + "=" + l2));
在MyBatis的核心配置文件中,可以通过以下的配置进行MyBatis的事务管理
<transactionManager type="JDBC">transactionManager>
JDBC事务管理器
MyBatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:
conn.setAutoCommit(false);//关闭SQL语句自动提交机制,开启事务。
......//业务逻辑
conn.commit();//提交事务
我们编写的mybatis与原生jdbc代码的对应关系
try {
SqlSession sqlSession = sqlSessionFactory.openSession(); //相当于:conn.setAutoCommit(false) ,关闭自动提交机制,开启事务
int count = sqlSession.Xxx("idName");//...执行DML语句
// ....可能有很多条DML语句
sqlSession.commit();//提交事务,相当于conn.commit()
} catch(Exception e) {
// 发生异常,回滚事务
sqlSession.rollback();//发生异常回滚事务
e.printStackTrace();//打印堆栈异常信息
}
上述《2》中使用的是SqlSessionFactory对象的openSession()无参数重载方法获取的SqlSession对象,
SqlSession sqlSession = sqlSessionFactory.openSession();
/*使用openSession的无参数重载方法底层调用的是:*/
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); // 这里传过去的是fasle,相当于conn.setAutoCommit(false),开启事务。
}
常用的重载方法还有openSession(boolean autoCommit)
SqlSession sqlSession = sqlSessionFactory.openSession(true);
/*使用openSession的这个重载方法底层调用的是:*/
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
//调用openSession的时候
//1.如果传过来的是true这里的autoCommit就是true,相当于conn.setAutoCommit(true),关闭事务。只要执行任意一条DML语句就提交一次。
//2.如果传过来的是false这里的autoCommit就是false,相当于conn.setAutoCommit(false),开启事务。这样就跟调用openSession的无参数构造方法是一个效果了。
}
JDBC默认情况下是没有事务的,只要不执行conn.setAutoCommit(false),那么就没有关闭自动提交机制,每执行一条DML语句就提交一次。【JDBC默认是自动提交的】
MANAGED事务管理器:
JUnit是一个开放源代码的Java测试框架,用于编写和运行可重复的测试。他是用于单元测试框架体系xUnit的一个实例(用于java语言)。
测试过程中设计到两个概念:期望值和实际值相同则表示测试通过,期望值和实际值不同则单元测试执行时会报错。
实现步骤:
第一步:在Maven工程中引入junit依赖
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
第二步:编写单元测试类【测试用例】,测试用例中每一个测试方法上都是用@Test注解进行标注。
测试类【测试用例】的名字:XxxTest,测试用例和被测试的类一般都在相同的包名下,只不过一个是在“main/java”下,一个是在“test/java”下。【一般是一个业务类对应一个测试用例,业务类中每一个方法都对应一个实际的业务,一个实际的业务对应一个测试方法】
测试用例中可能有多个测试方法,一般是被测试的类中一个业务对应一个测试方法。测试方法的方法头必须这么写
public void testXxx() {}
// 访问权限修饰符必须是public修饰,不是public的抛出异常
// 不能是静态的方法,方法头带有static运行测试时抛出异常
// 方法的返回值类型必须是void修饰的
// 方法名可以随意但是规范命名一般是“test+被测试的方法的方法名”
// 方法体中一般实例化一个被测试的业务类,调用业务类的方法,使用Assert.assertEquals(expected,actual)方法进行判断,expected是期望值,actual是实际值。
第三步:可以在测试类上执行也可以在测试方法上执行:
如果一个配置文件“main/resources”下面也有,“test/resources”中也有:遵循就近原则:
那么test中编写的测试类可以使用“main/resources”里面的资源。
main中的测试类不能使用“test/resources”里面的资源。
在MyBits核心配置文件中有个settings标签:
这是MyBatis非常重要的调整设置,他会改变MyBatis的运行时行为。其中有一项设置名是logImpl,用来指定MyBatis所用日志的具体实现,未指定的时候自动查找(从我们引入的依赖中自动查找有那些日志框架)
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
MyBatis常见的集成的日志组件有哪些?
其中STDOUT_LOGGING是标准日志,MyBatis已经实现了这种标准日志,mybatis框架本身已经实现了这种标准。只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
为什么使用的是STDOUT_LOGGING标准日志才需要配置setting,使用的是第三方的日志框架MyBatis就可以自动查找呐?
继承LohBack日志框架:
logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)
第一步:引入logback的依赖。
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
第二步:引入logback所必须的xml配置文件。
logback.xml配置文件的内容:
<configuration debug="false">
<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>
每一次获取SqlSession对象代码太繁琐,封装一个工具类。
工具类代码:
package com.powernode;
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 SqlSessionUtil () {}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 每调用一次openSession方法获取一个SqlSession对象
* 该SqlSession对象,不支持自动提交,是开启事务的状态
* @return 会话对象
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
}
工具类的测试用例:
package com.powernode;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class SqlSessionTest {
@Test
public void testOpenSession() {
SqlSession sqlSession = SqlSessionUtil.openSession();
int insertCar = sqlSession.insert("insertCar");
System.out.println("===>" + insertCar);
sqlSession.commit();
sqlSession.close();
}
}
分析以下SQL映射文件中的SQL存在的问题:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="car">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
insert>
mapper>
存在的问题是:SQL语句中的值不应该写死,值应该是用户提供的。之前的JDBC代码是这样写的:
// JDBC中使用 ? 作为占位符。那么MyBatis中会使用什么作为占位符呢?
String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(?,?,?,?,?)";
// ......
// 给 ? 传值。那么MyBatis中应该怎么传值呢?
ps.setString(1,"103");
ps.setString(2,"奔驰E300L");
ps.setDouble(3,50.3);
ps.setString(4,"2022-01-01");
ps.setString(5,"燃油车");
在MyBatis中可以这样做:用户填写表单提交了数据之后,后端Java程序获取到用户提交的数据之后,将用户提交的数据封装成一个对象,然后将对象中封装的数据动态映射成一条SQL语句,所以在MyBatis中指的是sql语句与实体对象之间的映射,这个实体对象就是封装数据的对象。
在MyBatis中给Mapping文件中的sql语句的占位符传值:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="asdasdasd">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_type)
values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
insert>
mapper>
delete方法有两个重载的方法:
sqlSession.delete(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了,sql语句的执行结果返回影响的数据库中的记录的条数。
sqlSession.delete(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句,sql语句的执行结果返回影响的数据库中的记录的条数。
第一个参数是执行的delete语句的sql语句的id属性值。
第二个参数数封装数据的对象,将这个对象中封装的数据映射成一条SQL语句。
<delete id="deleteByCarNum">
delete from t_car where id = #{SuiBianXie}
delete>
//Java程序这样写
SqlSession sqlSession = SqlSessionUtil.openSession();
//第二个参数直接把"占位符应该得到的这个值"传过去就行不用封装成pojo对象也不用存到Map集合中。代表将65传到“#{SuiBianXie}”位置。
int a = sqlSession.delete("deleteCarById",65);
//第二种方式:就是把数据放到对象中把对象映射成sql语句(板板正真的写)SQL语句中只有一个占位符的时候没有必要这么写(所有DML语句都包括在内),这么些比较麻烦
//Map map = new HashMap<>();
//map.put("SuiBianXie",64);
//int a = sqlSession.delete("deleteCarById",map);
System.out.println(a);
sqlSession.commit();
sqlSession.close();
update方法有两个重载的方法:
sqlSession.update(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了,sql语句的执行结果返回影响的数据库中的记录的条数。
sqlSession.update(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句,sql语句的执行结果返回影响的数据库中的记录的条数。
sql语句如下:
<update id="updateCarByPOJO">
update t_car set
car_num = #{carNum}, brand = #{brand},
guide_price = #{guidePrice}, produce_time = #{produceTime},
car_type = #{carType}
where id = #{id}
update>
java代码如下:
//这里使用的是pojo对象传值的,当然使用map集合传值也是可以的
Car car = new Car(2l,new Long(8888),"越野车686",88.8,"2015-09-08","燃油车11");
SqlSession sqlSession = SqlSessionUtil.openSession();
sqlSession.update("updateCarByPOJO",car);
sqlSession.commit();
sqlSession.close();
总结:给SQL语句的传值规则:
SqlSession对象的selectOne方法有两个重载的方法:
sqlSession.selectOne(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性指定的)。【这就是MyBatis的sql语句到对象的映射】
sqlSession.selectOne(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性指定的)。【这就是MyBatis的sql语句到对象的映射】
resultType这个属性是不能省略的:虽然可以确定返回的这个类型肯定是一个Object类型的数据,通过selectOne方法的返回值也可以看到,但是MyBatis获取到数据库中的数据之后,需要给我们封装成一个对象,这个对象中有查询结果集中的字段属性,这样的对象才有意义,显然Object类型中没有查询结果集中的字段属性,所以resultType这个属性是不能省略的,必须由我们规定将数据封装到哪一个对象中。
使用selectOne查询的时候,如果查询结果集中只有一条记录,那么就直接帮我们封装成select标签中resultType属性规定的类型的数据了,如果结果集中有多条数据,那么MyBatis会抛出异常。
实现过程:
写Mapping映射文件中的sql语句:
<select id="selectCarOne" resultType="com.xyh.pojo.Car">
select * from t_car where id = #{id}
select>
Java测试程序:
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行DQL语句,查询。根据ID查询,返回结果一定是一条。
// MyBatis底层执行了select语句之后,一定会返回一个结果集对象,在JDBC中叫ResultSet,接下来就是MyBatis从结果集中取出数据,封装成java对象(在resultType中指定封装成什么类型的对象),如果resultType中指定的类型的属性一个也没有查询结果集的字段,那么返回占位符的值,也就是selectOne的第二个参数
Object obj = sqlSession.selectOne("selectCarOne",1);//sql语句中只有一个占位符,直接将这个要传进去的数据写到这里,不用封装成pojo对象也不用封装成map集合。
System.out.println(obj);
// 查询属于DQL语句,没有事务,不用提交,直接关闭会话就行
sqlSession.close();
运行结果:Car{id=1, carNum=null, brand=‘宝马520Li’, guidePrice=null, produceTime=‘null’, carType=‘null’},
为什么有这么多null?因为查询结果集中的字段名和要封装成的对象的类型的属性名对应不上。
# 分析上述SQL语句的查询结果
mysql> select * from t_car where id =1;
+----+---------+-------------+-------------+-------------+-----------+
| id | car_num | brand | guide_price | produce_time | car_type |
+----+---------+-------------+-------------+-------------+-----------+
| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |
+----+---------+-------------+-------------+-------------+-----------+
# 以汽车编号为例:查询结果集的字段名是“car_num”,Mybatis会不区分大小写的去找对象中的“car_num”属性,发现对象中没有这个属性,所以也就没能赋上值,MyBatis他不知道与“car_num”字段对应的属性名对象中的“carNum”属性,所以“carNum”属性也没有被赋值,所以就是null了。
解决方法:使用as重命名查询结果集的字段名,让查询结果集的字段名和对象的属性名保持一致:
<select id="selectCarOne" resultType="com.xyh.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>
运行结果:Car{id=1, carNum=1001, brand=‘宝马520Li’, guidePrice=10.0, produceTime=‘2020-10-11’, carType=‘燃油车’}
总结:MyBatis找get方法的时候,get方法的方法名get后面的单词只能是首字母不区分大小写【给SQL映射文件中的占位符传值的时候MyBatis用到了get方法】。
MyBatis找set方法的时候,set后面的单词所有的字母都可以不区分大小写。
MyBatis找属性的时候,也是所有的单字母可也不区分大小写。
一种方法是使用SqlSession对象的selectList方法,selectList方法有三种重载形式:
实现过程
<select id="selectAllCar" resultType="com.xyh.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>
//java代码是这么写的
SqlSession sqlSession = SqlSessionUtil.openSession();
List<Car> cars = sqlSession.selectList("selectAllCar",null,new RowBounds(0,3));//没有占位符第二个参数可以传null,表示获取查询结果集中的前三条
cars.forEach(car -> System.out.println(car));//使用拉姆达表达式遍历集合
sqlSession.close();
在SQL Mapper配置文件中标签的namespace属性可以翻译为“命名空间”,这个命名空间只要是为了防止SqlId冲突的。
如下实例:
<mapper namespace="t_car1">
<select id="selectAllCar" resultType="com.xyh.pojo.Car">
select * from t_car
select>
mapper>
<mapper namespace="t_car2">
<select id="selectAllCar" resultType="com.xyh.pojo.Car">
select * from t_car
select>
mapper>
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行这条SQL语句的时候并不知道“selectAllCar”是CarMapping.xml文件中的SQL语句还是CarMapping2.xml文件中Sql语句,会报错。
//List
// 更改方案:所以需要在SQLID前面加上命名空间完整的写法是:命名空间.SqlId【namespace.SqlId】
List<Object> stus = sqlSession.selectList("t_car1.selectAllCar" | "t_car2.selectAllCar");
stus.forEach(stu -> System.out.println(stu));
sqlSession.close();
sql语句的Id完整写法是:namespace.SqlId
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="powernode">
<environment id="powernode">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
<environment id="xyh">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/xyh"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
<mapper resource="CarMapper2.xml"/>
mappers>
configuration>
configuration:根标签,表示配置信息。
environments:环境(多个),以‘s’结尾表示复数,也就是说MyBatis的运行环境可以配置多个数据源。environments标签可以有多个environment子标签。【以下是environments标签的属性和子标签】
default属性:表示默认使用的是那个环境,default的值可以是一个environment标签的id属性值。default的值只需要和environment的id值一致即可。
什么叫做默认?在使用会话工厂构建对象构建会话工厂对象的时候需要调用SqlSessionFactoryBuilder对象的build方法
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build("核心配置文件的输入流","使用的数据库环境的id值");//如果第二个参数没有规定那么,使用的就是默认的数据库环境
会话工厂对象(SqlSessionFactory):一般是一个数据库对象一个,在build的时候第二个参数指定数据库,就会创建出来这个数据库的会话工厂对象,会话工厂对象创建出来之后可以开启多个会话对象,多个会话对象可以对数据库(包括数据库中所有的表)进行多种不同的操作,所以没有必要以对一个数据库创建多个会话工厂对象。
environment(子标签):具体的环境(主要包括:事务管理器的配置+数据源的配置)。【以下是environment标签的属性和子标签】
mappers:在mappers标签中可以配置多个sql映射文件的路径【mappers标签的子标签如下】
mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到一个属性资源文件中,假设在类的根路径下创建jdbc.properties文件,配置如下:
jdbc.url=jdbc:mysql://localhost:3306/powernode
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
properties>
<environments default="powernode">
<environment id="powernode">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapping.xml"/>
<mapper resource="StudentMapping.xml">mapper>
mappers>
configuration>
引入Dom4j依赖
<dependencies>
<dependency>
<groupId>org.dom4jgroupId>
<artifactId>dom4jartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.2.0version>
dependency>
dependencies>
第二步:编写配置文件godbatis-config.xml
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
<mappers>
<mapper resource="sqlmapper.xml"/>
mappers>
environments>
configuration>
第三步:解析godbatis-config.xml
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 使用dom4j解析XML文件
*/
public class ParseXMLByDom4j {
@Test
public void testGodBatisConfig() throws Exception{
// 读取xml,获取document对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("godbatis-config.xml"));
// 获取标签的default属性的值
Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
String defaultId = environmentsElt.attributeValue("default");
System.out.println(defaultId);
// 获取environment标签
Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
// 获取事务管理器类型
Element transactionManager = environmentElt.element("transactionManager");
String transactionManagerType = transactionManager.attributeValue("type");
System.out.println(transactionManagerType);
// 获取数据源类型
Element dataSource = environmentElt.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println(dataSourceType);
// 将数据源信息封装到Map集合
Map<String,String> dataSourceMap = new HashMap<>();
dataSource.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
});
dataSourceMap.forEach((k, v) -> System.out.println(k + ":" + v));
// 获取sqlmapper.xml文件的路径
Element mappersElt = (Element) document.selectSingleNode("/configuration/environments/mappers");
mappersElt.elements().forEach(mapper -> {
System.out.println(mapper.attributeValue("resource"));
});
}
}
SAXReader对象是dom4j中解析xml的核心对象,调用SAXReader对象的read方法获取整个文档对象
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
//document就是整个文档
Document document = reader.read(resourceAsStream);
Document的selectSingleNode方法用于获取单个节点。
document.selectSingletonNode(String xpath);
/*
xpath:如果以“/”开始代表从根路径开始获取元素,如果以“//”开始代表从任意位置开始获取元素。
*/
document.getRootElement();//获取xml文档的根元素
Element对象的element方法用于获取该元素的特定子元素
element.element(String tagName);//获取标签名是“tagName”的子标签
element.elements();//获取所有的子标签
element.getElementTrim();//获取标签中的内容,并且去除前后空格
Element对象的attributeValue方法用于获取元素的属性值
element.attributeValue(String attrName);//获取attrName属性值
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
我们要使用javassist,首先要引入它的依赖。
<dependency>
<groupId>org.javassistgroupId>
<artifactId>javassistartifactId>
<version>3.20.0-GAversion>
dependency>
样例代码:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import java.lang.reflect.Method;
public class JavassistTest {
public static void main(String[] args) throws Exception {
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 创建类
CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");
// 创建方法
// 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
// 设置方法的修饰符列表
ctMethod.setModifiers(Modifier.PUBLIC);
// 设置方法体
ctMethod.setBody("{System.out.println(\"hello world\");}");
// 给类添加方法
ctClass.addMethod(ctMethod);
// 调用方法
Class<?> aClass = ctClass.toClass();
Object o = aClass.newInstance();
Method method = aClass.getDeclaredMethod("execute");
method.invoke(o);
}
}
高版本的JDK运行的时候需要加入两个参数:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED
使用MyBatis框架自带的工具类可以帮我们动态生成Dao实现类:我们就不需要写MyBatis的实现类了,但是MyBatis的工具类在生成DaoProxy字节码的时候不知道我们的Dao层的实现类的方法体中要执行的sql语句是什么,所以就需要通过sqlid来指定要执行的的sql语句是什么,sqlid是程序员写的,具有多变性,那怎么帮呐?所以MyBatis框架的开发者就出台了一个规定:凡是使用GenerateDaoPeoxy机制的,SqlId都不能随便写,namespace必须是dao层接口的全限定类名,id必须是dao接口中的方法名。【疑问:万一一个Dao实现类中的方法中需要执行两个sql语句,那么SqlId不久重复了吗,怎么办?】
使用Javassist动态生成DaoImpl类
package com.powernode.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.session.SqlSession;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class GenerateDaoByJavassist {
/**
* 根据dao接口生成dao接口的代理对象
*
* @param sqlSession sql会话
* @param daoInterface dao接口
* @return dao接口代理对象
*/
public static Object getMapper(SqlSession sqlSession, Class daoInterface) {
ClassPool pool = ClassPool.getDefault();
// 生成代理类
CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");
// 接口
CtClass ctInterface = pool.makeClass(daoInterface.getName());
// 代理类实现接口
ctClass.addInterface(ctInterface);
// 获取所有的方法
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// 拼接方法的签名
StringBuilder methodStr = new StringBuilder();
String returnTypeName = method.getReturnType().getName();
methodStr.append(returnTypeName);
methodStr.append(" ");
String methodName = method.getName();
methodStr.append(methodName);
methodStr.append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
methodStr.append(parameterTypes[i].getName());
methodStr.append(" arg");
methodStr.append(i);
if (i != parameterTypes.length - 1) {
methodStr.append(",");
}
}
methodStr.append("){");
// 方法体当中的代码怎么写?
// 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)
String sqlId = daoInterface.getName() + "." + methodName;
// 获取SqlCommondType
String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
if ("SELECT".equals(sqlCommondTypeName)) {
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");
methodStr.append("return (" + returnTypeName + ")obj;");
} else if ("UPDATE".equals(sqlCommondTypeName)) {
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");
methodStr.append("return count;");
}
methodStr.append("}");
System.out.println(methodStr);
try {
// 创建CtMethod对象
CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
// 将方法添加到类
ctClass.addMethod(ctMethod);
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
});
try {
// 创建代理对象
Class<?> aClass = ctClass.toClass();
Constructor<?> defaultCon = aClass.getDeclaredConstructor();
Object o = defaultCon.newInstance();
return o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
启动过程中显示,tomcat服务器自动添加了以下的两个运行参数。所以不需要再单独配置。
4.直接调用以下代码即可获取dao接口的代理类:[MyBatis可以帮助我们自动生成Dao层接口的实现类]
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
/*MyBatis中的getMapper()方法是用来获取Mapper接口的实例对象,这个实例对象是通过MyBatis框架自动生成的。Mapper接口中定义了一系列的查询语句,通过调用这些查询语句可以对数据库进行各种操作。
在MyBatis中,Mapper接口并没有具体的实现类,而是由框架在运行时动态生成的代理类。当我们调用getMapper()方法时,MyBatis会根据传入的Mapper接口类型,使用JDK动态代理技术动态生成一个该Mapper接口的实例。
这个实例会拦截所有方法调用,并将调用转发给MyBatis内部的SqlSession对象去执行具体的SQL语句。这样就可以很方便地进行数据库操作,而无需手动编写SQL语句和结果集映射代码。*/
namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致。
将service中获取dao对象的代码再次修改,如下:
这种方式仅适用于Dao层的实现类不用写了,因为Dao层的实现类代码固定并且没有业务,所以可以根据固定的方法生成,但是Service层的实现类不能使用这种方式生成实现类【MyBatis实际上采用了代理模式。在内存中通过javassist框架生成dao接口的代理类,然后创建代理类的实例】
如果使用MyBatis的话一般后端的dao层使用mapper层替代
dao层和mapper层里面的东西都是一样的,只不过一个叫做dao一个叫做mapper,别人一看mapper包就知道使用了MyBatis框架,可读性更强。比如Dao层中的一个接口叫做“CarDao”那么mapper层中的接口叫做“CarMapper”,只是名字不同而已。
总结使用MyBatis的接口代理机制的步骤:
如果一个接口方法对应两条不同的 SQL 语句,而这些 SQL 语句在映射文件中的 ID 仍然使用了方法名,那么就会导致 SQL ID 的重复,这时需要显式地为这些 SQL 语句指定不同的 ID。
一种解决方案是采用 MyBatis 提供的 @SelectProvider
注解来动态生成 SQL 语句。使用 @SelectProvider
可以将查询语句的构建逻辑放到一个单独的类中,这个类可以根据传入参数的不同来生成不同的 SQL 语句,并为每个 SQL 语句指定不同的 ID。
另一种解决方案是在 XML 映射文件中采用 标签的形式来定义 SQL 语句,而不是直接使用 ID。这样可以避免 ID 重复的问题,同时也可以更加灵活地定义 SQL 语句的内容和参数。
如果只是编写了映射器接口,没有编写与映射器接口中方法对应的SQL语句(或者namespace没有使用接口的全限定接口名,sqlid没有使用方法名),使用MyBatis接口代理机制的话,代理类通过namespace+sqlid找不到对应的sql语句就会抛出异常。
但是接口中的默认方法可以没有与之对应的SQL映射文件中的SQL语句也可以执行
#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进性sql语句的拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象,只有在需要进行sql语句关键字拼接的情况下才会用到。
${}是先把pojo对象中的值取出来然后再拼接成一条SQL语句,最后再编译这条SQL语句,执行sql语句。
#{}是先把sql语句编译了,有占位符?,然后再把pojo对象中的值取出来给占位符传值。zhi行sql语句。
同一条SQL语句中,KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}都有的时候先执行{},后执行#{}
如果pojo对象的属性(map集合的value值)是“字符串”:给SQL映射文件中的两种占位符传值的时候
无论是${}也好还是#{}也好都是从对象中取数据的。都是MyBatis中的占位符。
可以从pojo对象中取数据
可以从map即可中取数据
为什么${}可以写在单引号里面而#{}就不可以呐?
mybatis在检查sql映射文件中的sql语句的时候不管是否在字符串里面只要有占位符就进行处理:
核心逻辑:${}先拼接后编译,#{}先替换后传值。
依赖
<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>com.powernodegroupId>
<artifactId>mybatis-005-anticartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
dependencies>
<properties>
<maven.compiler.source>17maven.compiler.source>
<maven.compiler.target>17maven.compiler.target>
properties>
project>
jdbc.properties放在类的根路径下
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
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;
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
}
local.remove();
}
}
pojo
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
// 构造方法
// set get方法
// toString方法
}
mapper接口
import java.util.List;
public interface CarMapper {
List<Car> selectByCarType(String carType);
}
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="dev">
<environment id="dev">
<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>
CarMapper.xml,SQL映射文件,放在类的根路径下:注意namespace必须和接口名一致,id必须和接口中的方法名一致。【注意:因为MyBatis在内存中文给我们生成的Mapper层的实现类也需要调用SQL映射文件中的sql语句完成功能,所以MyBatis就必须知道sql语句的id是什么才能找到对应的sql语句并且执行sql语句。又因为SQL语句的id是使用MyBatis框架的程序员提供的具有多变性,所以MyBatis规定如果要是使用MyBatis的接口代理机制,namespace就必须是接口的全限定接口名,sqlid必须是接口中的方法名,这样MyBatis就确定下来了SQL映射文件中的sql语句的id】
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = #{carType}
select>
mapper>
测试程序
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByCarType(){
//调用SqlSession对象的getMapper(Class c)方法,MyBatis就可以帮助我们生成接口的代理类,也就是以前的Dao层接口的实现类,传进去的参数是接口类型的字节码
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByCarType("燃油车");
cars.forEach(car -> System.out.println(car));
}
}
运行结果
这就是#{}他会先编译SQL语句,然后再给占位符传值。
同样的需求,我们使用${}来完成
CarMapper.xml文件修改如下
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = ${carType}
select>
mapper>
再次运行测试程序:
出现异常了,这是为什么呢?看看生成的sql语句:
很显然, 是先进行 s q l 语句的拼接,然后再编译,出现语法错误是正常的,因为燃油车是一个字符串,在 s q l 语句中应该添加单引号, {} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号, 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为燃油车是一个字符串,在sql语句中应该添加单引号,{}取出来的值进行sql语句拼接的时候,不带单引号。
修改:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = '${carType}'
select>
mapper>
再执行测试程序:
通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。
原则:能用 #{} 就不用 ${}
CarMapper接口:
List<Car> selectAll(String ascOrDesc);
CarMapper.xml文件:
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum #{key}
select>
测试程序
@Test
public void testSelectAll(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll("desc");
cars.forEach(car -> System.out.println(car));
}
运行结果
报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:
select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum ‘desc’
因为使用#{}方式传值的话,会把String类型的属性带上双引号已经编译过的sql语句。
desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}
CarMapper.xml
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum ${key}
select>
再次运行测试程序
业务背景:一次删除多条记录
对应的sql语句
#当然完全可以弄三个占位符来给id赋值
delete from t_user where id = 1 or id = 2 or id = 3;
#这里为了描述#{}和${}的区别就先使用这种方式
delete from t_user where id in (1,2,3)
假设现在使用in的这种处理方法,前端传过来的字符串是:1,2,3.如果使用mybatis处理,应该使用#{} 还是 ${}
使用#{} :delete from t_user where id in(‘1,2,3’) 执行错误:1292 - Truncated incorrect DOUBLE value: ‘1,2,3’
使用${} :delete from t_user where id in(1, 2, 3)
代码
//Mapper接口中的方法
int deleteBatch(String ids);
//SQL映射文件中的SQL语句
<delete id="deleteBatch">
// 这里必须使用 ${id}
delete from t_car where id in(${ids})
</delete>
//测试程序
@Test
public void testDeleteBatch(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatch("1,2,3");
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
运行结果:
//使用MyBatis接口代理机制,接口中的方法
List<Car> selectLikeByBrand(String likeBrank);
//MyBatis的SQL映射文件的sql语句
<select id="selectLikeByBrand" 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
brand like '%${brand}%' //先将${brand}的值从pojo对象中取出来${brand}的值也就是“奔驰”,拼接之后的sql语句就是 ... brand like '%奔驰%'。
</select>
//测试程序
@Test
public void testSelectLikeByBrand(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectLikeByBrand("奔驰");
cars.forEach(car -> System.out.println(car));
}
运行结果:
<select id="selectLikeByBrand" 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
brand like concat('%',#{brand},'%')
select>
运行结果:
<select id="selectLikeByBrand" 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
brand like "%"#{brand}"%"
select>
MySQL原理:
select * from t_car where car_type like “%” “油” “%”,如果like后面有多个字符串(多个字符串之间可以使用空格隔开,也可以不使用空格隔开),那么DBMS就会把like后面的多个字符串看成是一个字符串。上面这句sql,效果等同于select * from t_car where car_type like “%油%”
我们先来观察一下MyBatis的SQL映射文件CarMapper.xml中的配置信息:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
order by carNum ${key}
select>
<select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from
t_car
where
car_type = '${carType}'
select>
mapper>
resultType属性用来指定查询结果集的封装类型,这个名字太长,可以起别名。
在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:
<typeAliases>
<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
typeAliases>
首先要注意typeAlias标签的放置位置,如果放错了,可以看看错误提示信息。
typeAliases标签中的typeAlias可以写多个。
<typeAliases>
<typeAlias type="com.xyh.pojo.Car" alias="Car">typeAlias>
<typeAlias type="com.xyh.pojo.Student" alias="Student">typeAlias>
typeAliases>
如果一个包下类太多,每个类都要起别名,会导致typeAlias标签配置较多,所以MyBatis提供了package的配置方式,只需要指定包名,该包下所有的类都自动起别名,别名就是简类名。并且别名都不区分大小写。
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
typeAliases>
package也可以配置多个的。
在SQL映射文件中用一下
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<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
order by carNum ${key}
select>
<select id="selectByCarType" 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
car_type = '${carType}'
select>
mapper>
namespace属性不能使用别名,必须是Mapper接口的全限定接口名。
这种方式是从类路径中加载配置文件,所以这种方式要求SQL映射文件必须放在resources目录下。
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的为止没有要求,随意。这种方式使用较少,移植性差。
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
mappers>
如果使用这种方式必须满足以下条件:
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
mappers>
放到Java下和放到resource下都是放到了类的根路径下,SQL映射文件放resource下,Mapper接口放到java下。
在resource下新建目录的时候使用“/”隔开,在java下新建目录的时候使用“.”隔开
为什么使用class属性这种方式即需要SQL映射文件的文件名和接口的简类名一致有需要和接口放到同一个包下这么多约束要求,还要用这种方式?
答:因为这种方式是为了后续使用package机制作准备的。class属性名规定的是接口的全限定名,一旦使用package指定了某个包,这个包下所有的接口都被class属性指定是一个效果。因为resource属性和url属性都没有package机制,所以即使有这么多约束这么多要有我们也要使用class属性也要使用package机制。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="" alias="">typeAlias>
<typeAlias type="" alias="">typeAlias>
<typeAlias type="" alias="">typeAlias>
....
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/${数据库的名字}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper class="">mapper>
<mapper class="">mapper>
<mapper class="">mapper>
.....
mappers>
configuration>
下面是使用package机制的代码:
这里使用了包扫描机制:如果name属性值是“com.xyh”那么MyBatis会扫描“com.xyh”包以及“com.xyh”包的子包。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name=""/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/${数据库的名字}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<package name=""/>
mappers>
configuration>
mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文件。
//Mapper接口中的方法
void insertUseGeneratedKeys(Car car);
//SQL映射文件中共的SQL语句
/*useGeneratedKeys="true"表示使用生成的主键,keyProperty="id"表示生成的主键放到pojo对象的id属性中*/
<insert id="insertUseGeneratedKeys" 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 testInsertUseGeneratedKeys() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = new Car();
car.setCarNum("5262");
car.setBrand("BYD汉");
car.setGuidePrice(30.3);
car.setProduceTime("2020-10-11");
car.setCarType("新能源");
mapper.insertUseGeneratedKeys(car);
SqlSessionUtil.openSession().commit();
System.out.println(car.getId());
}
映射器就是Mapper接口的实现类。如果使用MyBatis的接口代理机制的话,这个实现类(映射器)是MyBatis帮我们实现的,我们只需要调用SqlSession对象的getMapper(Mapper接口的Class对象),MyBatis就可以帮我们把映射器创建出来
【UserMapper mapper = sqlSession.getMapper(UserMapper.class);】mapper映射器可以帮我们完成java对象和SQL语句之间的相互转换(映射)。
表:t_student
表中现有数据:
pojo类:
import java.util.Date;
public class Student {
private Long id;
private String name;
private Integer age;
private Double height;
private Character sex;
private Date birth;
// constructor
// setter and getter
// toString
}
简单类型包括:
MyBatis框架实际上内置了很多别名。可以参考开发手册。
需求:根据name查、根据id查、根据birth查、根据sex查
import com.powernode.mybatis.pojo.Student;
import java.util.Date;
import java.util.List;
//映射器接口
public interface StudentMapper {
/**
* 根据name查询
* @param name
* @return
*/
List<Student> selectByName(String name);
/**
* 根据id查询
* @param id
* @return
*/
Student selectById(Long id);
/**
* 根据birth查询
* @param birth
* @return
*/
List<Student> selectByBirth(Date birth);
/**
* 根据sex查询
* @param sex
* @return
*/
List<Student> selectBySex(Character sex);
}
//SQL映射文件
<?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">
//namespace必须是接口全限定名
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<select id="selectByName" resultType="student">
select * from t_student where name = #{name}
</select>
<select id="selectById" resultType="student">
select * from t_student where id = #{id}
</select>
<select id="selectByBirth" resultType="student">
select * from t_student where birth = #{birth}
</select>
<select id="selectBySex" resultType="student">
select * from t_student where sex = #{sex}
</select>
</mapper>
//测试程序
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class StudentMapperTest {
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
@Test
public void testSelectByName(){
List<Student> students = mapper.selectByName("张三");
students.forEach(student -> System.out.println(student));
}
@Test
public void testSelectById(){
Student student = mapper.selectById(2L);
System.out.println(student);
}
@Test
public void testSelectByBirth(){
try {
Date birth = new SimpleDateFormat("yyyy-MM-dd").parse("2022-08-16");
List<Student> students = mapper.selectByBirth(birth);
students.forEach(student -> System.out.println(student));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Test
public void testSelectBySex(){
List<Student> students = mapper.selectBySex('男');
students.forEach(student -> System.out.println(student));
}
}
通过测试得知,简单类型对于mybatis来说都是可以自动类型识别的:
也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是ps.setInt()。它可以自动推断。因为MyBatis肯定能通过接口的字节码文件拿到方法的参数的类型,通过方法中参数的类型确定调用哪个setXxx给占位符传值。
其实SQL映射文件中的配置比较完整的写法是:
<select id="selectByName" resultType="student" parameterType="java.lang.String">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
select>
其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
如果参数只有一个的话,#{} 里面的内容就随便写了。对于 ${} 来说,注意加单引号。
需求:根据name和age查询
//映射器接口中的方法
List<Student> selectByParamMap(Map<String,Object> paramMap);
//SQL映射文件
<select id="selectByParamMap" resultType="student">
//#{nameKey}:大括号中写的是map集合中的key,取出来的是map集合中的value,到目前为止#{}和${}都是为了从map集合或者pojo对象中取数据
//#{这里可以写中文}
select * from t_student where name = #{nameKey} and age = #{ageKey}
</select>
//测试程序
@Test
public void testSelectByParamMap(){
// 准备Map
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("nameKey", "张三");
paramMap.put("ageKey", 20);
List<Student> students = mapper.selectByParamMap(paramMap);
students.forEach(student -> System.out.println(student));
}
**这种方式是手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。**目前map集合的key必须是String类型的数据,实际上MyBatis底层再处理多参数问题的时候底层的map集合的key也是String类型的,再#{这里}写的时候去掉String类型的数据的“双引号”
需求:插入一条Student数据
//映射器接口中的方法
int insert(Student student);
//SQL映射文件中的SQL语句
<insert id="insert">
//#{name}:大括号中写的是pojo对象的属性名或者是getter方法去掉get首字母小写的英文单词。pojo对象中getter方法和属性只要有其中一个#{name}就可以取到值
insert into t_student values(null,#{name},#{age},#{height},#{birth},#{sex})
</insert>
//测试程序
@Test
public void testInsert(){
Student student = new Student();
student.setName("李四");
student.setAge(30);
student.setHeight(1.70);
student.setSex('男');
student.setBirth(new Date());
int count = mapper.insert(student);
SqlSessionUtil.openSession().commit();
}
这里需要注意的是:#{} 里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。
需求:通过name和sex查询Student
//映射器接口中的方法
List<Student> selectByNameAndSex(String name, Character sex);
//测试程序
@Test
public void testSelectByNameAndSex(){
List<Student> students = mapper.selectByNameAndSex("张三", '女');
students.forEach(student -> System.out.println(student));
}
//SQL映射文件中的SQL语句
<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{name} and sex = #{sex}
</select>
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPVtHPU3-1685102541189)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522171518541.png)]
修改StudentMapper.xml配置文件:尝试使用[arg1, arg0, param1, param2]去参数
<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{arg0} and sex = #{arg1}
select>
再次尝试修改StudentMapper.xml文件
<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{arg0} and sex = #{param2}
select>
通过测试可以看到:
实现原理:实际上在mybatis底层会创建一个map集合,以arg0/param1为key,以方法上的参数值为value,例如以下代码:
Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}
注意:**使用mybatis**3.4.2之前的版本时:要用#{0}和#{1}这种形式。
可以不用arg0 arg1 param1 param2吗?这个map集合的key我们自定义可以吗?当然可以。使用@Param注解即可。这样可以增强可读性。
需求:根据name和age查询
//映射器接口中的方法,第一个参数key值定义为“name”,第二个参数key值定义为“age”
List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
@Test
public void testSelectByNameAndAge(){
List<Student> stus = mapper.selectByNameAndAge("张三", 20);
stus.forEach(student -> System.out.println(student));
}
<select id="selectByNameAndAge" resultType="student">
//取出来的时候可以使用name和age也可以使用param1和param2,也可以混合着用,如果使用@Param注解之后就不能使用arg0...了。
select * from t_student where name = #{name} and age = #{age}
</select>
核心:@Param(“这里填写的其实就是map集合的key”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCxtVGww-1685102541190)(D:\Users\XueYingHao\MyBatis\老杜MyBatis\document\004-Param注解源码分析.png)]
@Param源码分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xa7R7XxS-1685102541190)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522171139630.png)]
如果映射器接口方法中参数只有一个的话
但是只有一个参数的时候也可以使用@Param注解,一旦使用了这个注解:
如果有多个参数的话:多个参数其中有pojo类型或者map类型的参数那么可以使用:以下这几种方式区分开:
如果查询结果集是一条,用List用多个的容器去接收那没问题。
如果查询结果集是多条,用Car类型的对象去接收会报错。
当查询的结果,有对应的实体类。并且查询结果只有一条的时:
映射器接口文件
public interface CarMapper {
Car selectById(Long id);
}
SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
select>
mapper>
测试程序:
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class CarMapperTest {
@Test
public void testSelectById(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = mapper.selectById(35L);
System.out.println(car);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tdzg3HI6-1685102541191)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522175119712.png)]
查询结果是一条的话可以使用List集合接收吗?当然可以。
//映射器接口文件
List<Car> selectByIdToList(Long id);
SQL映射文件
<select id="selectByIdToList" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
select>
测试程序:
@Test
public void testSelectByIdToList(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByIdToList(35L);
System.out.println(cars);
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CnWapkm-1685102541191)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522175255010.png)]
当查询的记录条数是多条的时候,必须使用集合接收。如果使用单个实体类接收会出现异常。
//映射器接口文件
List<Car> selectAll();
SQL映射文件
<select id="selectAll" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
select>
测试程序
@Test
public void testSelectAll(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7DvVwOc-1685102541191)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522175422206.png)]
当查询结果返回的数据,没有合适的实体类对应的话,可以采用Map集合来接收。查询结果字段名叫做map集合的key,字段值叫做map集合的value。
查询如果可以保证只有一条数据,则返回一个Map集合即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvBAbDK4-1685102541192)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522180844393.png)]
映射器接口文件
Map<String, Object> selectByIdRetMap(Long id);
SQL映射文件
<select id="selectByIdRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
select>
测试程序:
@Test
public void testSelectByIdRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<String,Object> car = mapper.selectByIdRetMap(35L);
System.out.println(car);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAsGN20R-1685102541192)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522181059542.png)]
如果返回一个Map集合,可以将Map集合放到List集合中吗?当然可以,在这里map集合充当的就是pojo类。
如果返回多条记录的话,只采用单个Map接收,这样同样会出现异常,一个Map对应一条记录,这里我们可以把Map放到List集合当中。SQL映射文件中共的resultType类型指定的还是"map"【List集合中存放的数据】。
查询结果条数大于等于1条数据,则可以返回一个存储Map集合的List集合。List等同于List
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2a4xghek-1685102541192)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522181700884.png)]
//映射器接口文件
List<Map<String,Object>> selectAllRetListMap();
SQL映射文件
<select id="selectAllRetListMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
select>
测试程序:
@Test
public void testSelectAllRetListMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Map<String,Object>> cars = mapper.selectAllRetListMap();
System.out.println(cars);
}
运行结果:
[
{carType=燃油车, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=33, brand=奔驰E300L},
{carType=电车, carNum=102, guidePrice=30.23, produceTime=2018-09-10, id=34, brand=比亚迪汉},
{carType=燃油车, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=35, brand=奔驰E300L},
{carType=燃油车, carNum=103, guidePrice=33.23, produceTime=2020-10-11, id=36, brand=奔驰C200},
......
]
拿Car的id做key,以后取出对应的Map集合时更方便。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3QMd3qP-1685102541193)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522204639823.png)]
实现过程
映射器接口中的方法:
@MapKey("id") //这里必须加上MapKey注解,不加的话那么就MyBatis就会以为我们要返回一条数据,就按照返回一条数据的逻辑帮我们生成Mapper接口的代理类,如果返回多条数据,就会抛出异常
//@MapKey("id") 中的id是数据库查询结果集中的字段名,并不是pojo对象的属性名,如果要是指定的查询结果集的列明不存在那么返回null,这个大map集合的key就是null。查出来多条记录,后面的记录会覆盖前面的记录。
Map<Long,Map<String,Object>> selectAllRetMap();
SQL映射文件:
<select id="selectAllRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
select>
测试程序:
@Test
public void testSelectAllRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<Long,Map<String,Object>> cars = mapper.selectAllRetMap();
System.out.println(cars);
}
运行结果:
{
64={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=64, brand=丰田霸道},
66={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=66, brand=丰田霸道},
67={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=67, brand=丰田霸道},
69={carType=燃油车, carNum=133, guidePrice=50.30, produceTime=2020-01-10, id=69, brand=丰田霸道},
......
}
//映射器接口中的方法
List<Car> selectAllByResultMap();
SQL映射文件
<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标签使用的是“resultType属性”,如果查询结果集的字段名和pojo对象的属性名对不上那么可以使用“resultMap属性”
测试程序:
@Test
public void testSelectAllByResultMap(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByResultMap();
System.out.println(cars);
}
使用这种方式的前提是:属性遵循Java命名规范,数据库表名遵循SQL命名规范。
比如以下的对应关系:
实体类中的属性名 | 数据库表的列名 |
---|---|
carNum | car_num |
carType | car_type |
produceTime | produce_time |
如何启用该功能,在mybatis-config.xml文件中进行配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
映射器接口中的方法
List<Car> selectAllByMapUnderscoreToCamelCase();
SQL映射文件
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select * from t_car
select>
这里还是接着用resultType就可以,因为开启驼峰命名自动映射之后,MyBatis知道我们的命名规则是标准的,它可以帮助我们自动完成字段到属性的映射,不需要我们再resultMap标签中写映射规则,所以这里不需要使用resultMap属性。
测试程序:
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase();
System.out.println(cars);
}
需求:查询总记录条数
实现过程
映射器接口中的方法:
Long selectTotal();
SQL映射文件
<select id="selectTotal" 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);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6obUh6E-1685102541193)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230522211746717.png)]
有的业务场景,需要sql语句进行动态拼接,例如:
批量删除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HsCS9EOF-1685102541194)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523171004329.png)]
delete from t_car where id in(1,2,3,4,5,6,.....这里的值是动态的,根据用户选择的id不同,值是不同的)
多条件查询
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H8X9P387-1685102541194)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523171047044.png)]
需求:多条件查询
可能包括的条件:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
映射器接口文件
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CarMapper {
/**
* 根据多条件查询Car
* @param brand 品牌
* @param guidePrice 指导价格
* @param carType 汽车类型
* @return
*/
List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
}
SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectByMultiCondition" resultType="car">
select * from t_car where
<if test="brand != null and brand != ''">
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>
mapper>
测试程序:
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByMultiCondition(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiCondition("丰田", 20.0, "燃油车");
System.out.println(cars);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tb44zV1-1685102541194)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523184952746.png)]
如果第一个条件为空,剩下两个条件不为空,会是怎样呢?
List<Car> cars = mapper.selectByMultiCondition("", 20.0, "燃油车");
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-499KhLZf-1685102541195)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523185037864.png)]
报错了,SQL语法有问题,where后面出现了and。这该怎么解决呢?
可以在where后面添加一个恒成立的条件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZBUKy5X-1685102541196)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523185357734.png)]
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdGGVDoQ-1685102541196)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523185415194.png)]
因为只有if标签的话,总是需要在where后面添加一个恒成立的条件【比如上例中的1=1】,所以出现了where标签,where标签的作用:让where子句更加动态智能。
需求:多条件查询:查询的条件是:汽车品牌、指导价格、汽车类型
映射器接口中的方法:
List<Car> selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
SQL映射文件
<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>
测试程序:
@Test
public void testSelectByMultiConditionWithWhere(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 20.0, "燃油车");
System.out.println(cars);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fr5FOgij-1685102541197)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523202322679.png)]
如果所有条件都是空呢?
List<Car> cars = mapper.selectByMultiConditionWithWhere("", null, "");
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WxNiAJXE-1685102541198)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523202352874.png)]
它可以自动去掉前面多余的and,那可以自动去掉后面多余的and吗?
<select id="selectByMultiConditionWithWhere" resultType="car">
select * from t_car
<where>
<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>
where>
select>
// 让最后一个条件为空
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 20.0, "");
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DfzkXPZo-1685102541199)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523202449355.png)]
很显然他不会去除后面多余的and或者or
因为where标签只能去掉sql语句前面多余的and或者or,但是不能去除sql语句后面多余的and或者or,where标签可以动态的添加where字句。
trim标签的属性:下面写四个属性都是对整个trim中的内容进行操作的,并不是某一个if分支或者是其他分支,是对整个trim标签中的内容进行操作的。
映射器接口文件:
List<Car> selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
SQL映射文件
<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>
测试程序:
@Test
public void testSelectByMultiConditionWithTrim(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiConditionWithTrim("丰田", 20.0, "");
System.out.println(cars);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5B9pfq7-1685102541199)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523210315849.png)]
如果所有条件为空,where会被加上吗?
List<Car> cars = mapper.selectByMultiConditionWithTrim("", null, "");
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3bso0jJ-1685102541200)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523210353944.png)]
主要使用在update语句当中,用来生成set关键字,同时去掉整个set标签中的内容最后多余的“,”,或者整个set标签中所有的内容最前面多余的“,”
比如我们只更新前端提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。
映射器接口的中的方法:
int updateWithSet(Car car);
SQL映射文件:
<update id="updateWithSet">
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>
测试程序:
@Test
public void testUpdateWithSet(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = new Car(38L,"1001","丰田霸道2",10.0,"",null);
int count = mapper.updateWithSet(car);
System.out.println(count);
SqlSessionUtil.openSession().commit();
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANlMo2lV-1685102541200)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230523212430864.png)]
choose、when、otherwise这三个标签实在一起使用的:
<choose>
<when>when>
<when>when>
<when>when>
<otherwise>otherwise>
choose>
<choose>
<when>when>
<when>when>
<when>when>
choose>
<choose>
<otherwise>otherwise>
choose>
等同于:
if(){
}else if(){
}else if(){
}else if(){
}else{
}
只有一个分支会被选择!!!
需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生产日期查询。
映射器接口中的方法 :
/**
* 使用choose when otherwise标签查询
* @param brand 汽车品牌
* @param guidePrice 指导价格
* @param produceTime 生产日期
* @return 符合条件的汽车列表
*/
List<Car> selectWithChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("produceTime") String produceTime);
SQL映射文件:
<select id="selectWithChoose" 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>
<otherwise>
produce_time >= #{produceTime}
otherwise>
choose>
where>
select>
测试程序:
@Test
public void testSelectWithChoose(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
//List cars = mapper.selectWithChoose("丰田霸道", 20.0, "2000-10-10");
//List cars = mapper.selectWithChoose("", 20.0, "2000-10-10");
//List cars = mapper.selectWithChoose("", null, "2000-10-10");
List<Car> cars = mapper.selectWithChoose("", null, "");
System.out.println(cars);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ns4Lgtq-1685102541201)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230524120151213.png)]
循环数组或者集合,生成动态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','新能源')
映射器接口中的方法:
/**
* 通过foreach完成批量删除
* @param ids 需要删除的记录的id数组
* @return
*/
int deleteBatchByForeach(@Param("ids") Long[] ids);
SQL映射文件:
<delete id="deleteBatchByForEach">
delete from t_car
<where>
<trim prefix="id in">
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
trim>
where>
delete>
测试程序:
@Test
public void testDeleteBatchByForeach(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatchByForeach(new Long[]{40L, 41L, 42L});
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaO98gZs-1685102541201)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230524125807034.png)]
int deleteBatchByForeach2(@Param("ids") Long[] ids);
<delete id="deleteBatchByForeach2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id = #{id}
</foreach>
</delete>
@Test
public void testDeleteBatchByForeach2(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatchByForeach2(new Long[]{40L, 41L, 42L});
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AJk9rGT-1685102541202)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230524125919266.png)]
/**
* 批量添加,使用foreach标签
* @param cars
* @return
*/
int insertBatchByForeach(@Param("cars") List<Car> cars);
<insert id="insertBatchByForeach">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
</foreach>
</insert>
@Test
public void testInsertBatchByForeach(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car1 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油车");
Car car2 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油车");
Car car3 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油车");
List<Car> cars = Arrays.asList(car1, car2, car3);
int count = mapper.insertBatchByForeach(cars);
System.out.println("插入了几条记录" + count);
SqlSessionUtil.openSession().commit();
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6kfd5wC-1685102541202)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230524130015596.png)]
sql标签用来生命sql片段
include标签用来将声明的sql片段包含到某个sql语句当中
作用:代码复用。容易维护。
<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carTypesql>
<select id="selectAllRetMap" resultType="map">
select <include refid="carCols"/> from t_car
select>
<select id="selectAllRetListMap" resultType="map">
select <include refid="carCols"/> carType from t_car
select>
<select id="selectByIdRetMap" resultType="map">
select <include refid="carCols"/> from t_car where id = #{id}
select>
准备数据库表:一个班级对应多个学生。班级表:t_clazz。学生表:t_student
创建pojo类student类和class类
package com.powernode.mybatis.pojo;
/**
* 学生类
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class Student {
private Integer sid;
private String sname;
//......
}
package com.powernode.mybatis.pojo;
/**
* 班级类
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class Clazz {
private Integer cid;
private String cname;
//......
}
创建mapper接口:StudentMapper、ClazzMapper
创建mapper映射文件:StudentMapper.xml、ClazzMapper.xml
pojo类Student中添加一个属性:Clazz clazz;表示学生关联的班级对象。
public class Student {
private Integer sid;
private String sname;
private Clazz clazz;
//......
}
SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<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="selectBySid" resultMap="studentResultMap">
select s.*, c.* from t_student s join t_clazz c on s.cid = c.cid where sid = #{sid}
select>
mapper>
测试程序:
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class StudentMapperTest {
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
Student student = mapper.selectBySid(1);
System.out.println(student);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnfLaTsB-1685102541202)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230524193000725.png)]
其他位置都不需要修改,只需要修改resultMap中的配置:association即可。
<resultMap id="studentResultMap" 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>
其他位置不需要修改,只需要修改以及添加以下三处:
第一处:association中的select位置填写sqlid。sqlid=namespace+id。其中column属性作为这条子sql语句的条件。
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectBySid" resultMap="studentResultMap">
select s.* from t_student s where sid = #{sid}
select>
第二处:在ClazzMapper接口中添加方法
public interface ClazzMapper {
/**
* 根据cid获取Clazz信息
* @param cid
* @return
*/
Clazz selectByCid(Integer cid);
}
第三处:在ClazzMapper.xml文件中进行配置
<mapper namespace="com.powernode.mybatis.mapper.ClazzMapper">
<select id="selectByCid" resultType="Clazz">
select * from t_clazz where cid = #{cid}
select>
mapper>
执行结果,可以很明显看到先后有两条sql语句执行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2N0Fporf-1685102541203)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525111200259.png)]
分步的优点:
什么是延迟加载?
想要支持延迟加载,非常简单。只需要在association标签中添加fetchType="lazy"即可。
修改StudentMapper.xml文件:
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz" select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"
fetchType="lazy"/>
resultMap>
fetchType属性有两个属性值:
在association标签中配置的fetchType=“lazy”,是局部的设置,只是对当前的association关联的sql语句起作用。
在实际开发中大部分是需要进行延迟加载的,所以建议开启全局的延迟加载机制:在MyBatis核心配置文件中添加全局配置:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
实际开发中把全局的延迟加载打开,如果某一步不需要进行延迟加载,请在association标签中设置:fetchType=“eager”
我们现在只查询学生名字,修改测试程序:
public class StudentMapperTest {
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
Student student = mapper.selectBySid(1);
//System.out.println(student);
// 只获取学生姓名
String sname = student.getSname();
System.out.println("学生姓名:" + sname);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KF60quab-1685102541203)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525130018230.png)]
如果后续需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句,修改测试程序:
public class StudentMapperTest {
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
Student student = mapper.selectBySid(1);
//System.out.println(student);
// 只获取学生姓名
String sname = student.getSname();
System.out.println("学生姓名:" + sname);
// 到这里之后,想获取班级名字了
String cname = student.getClazz().getCname();
System.out.println("学生的班级名称:" + cname);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T00NHF1e-1685102541204)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525130044031.png)]
通过以上的执行结果可以看到,只有当使用到班级名称之后,才会执行关联的sql语句,这就是延迟加载。
在mybatis中如何开启全局的延迟加载呢?需要setting配置,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E575To5S-1685102541205)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525130128433.png)]
一对多的实现,通常是在一的一方中有一个List集合属性。
在Clazz类中添加List stus;属性
public class Clazz {
private Integer cid;
private String cname;
private List<Student> stus;
// set get方法
// 构造方法
// toString方法
}
一对多的实现通常包括两种方式:
映射器接口中的方法:
public interface ClazzMapper {
/**
* 根据cid获取Clazz信息
* @param cid
* @return
*/
Clazz selectByCid(Integer cid);
/**
* 根据班级编号查询班级信息。同时班级中所有的学生信息也要查询。
* @param cid
* @return
*/
Clazz selectClazzAndStusByCid(Integer cid);
}
SQL映射文件:
<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="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c join t_student s on c.cid = s.cid where c.cid = #{cid}
select>
测试程序:
@Test
public void testSelectClazzAndStusByCid() {
ClazzMapper mapper = SqlSessionUtil.openSession().getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectClazzAndStusByCid(1001);
System.out.println(clazz);
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ImISNMPg-1685102541205)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525154218889.png)]
修改以下三个位置即可:
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus"
select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c where c.cid = #{cid}
select>
StudentMapper.java文件中的方法
/**
* 根据班级编号获取所有的学生。
* @param cid
* @return
*/
List<Student> selectByCid(Integer cid);
StudentMapper.xml文件中的sql语句
<select id="selectByCid" resultType="Student">
select * from t_student where cid = #{cid}
select>
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkFDLKwQ-1685102541206)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525155602064.png)]
一级缓存默认是开启的。不需要做任何配置。
原理:只要使用相同的SqlSession对象执行同一条SQL语句,就会走缓存。
映射器接口中的方法:
import com.powernode.mybatis.pojo.Car;
public interface CarMapper {
/**
* 根据id获取Car信息。
* @param id
* @return
*/
Car selectById(Long id);
}
SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select * from t_car where id = #{id}
select>
mapper>
测试程序:
@Test
public void testSelectById() throws Exception{
// 注意:不能使用我们封装的SqlSessionUtil工具类。
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
//不是同一个SqlSession对象,是不同的缓存区域
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(83L);
System.out.println(car3);
CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
Car car4 = mapper4.selectById(83L);
System.out.println(car4);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1kAQXve-1685102541206)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525203839608.png)]
什么情况下不走缓存?
一级缓存失效包括两种方式:
第一种:两次查询之间手动清空了一级缓存。
sqlSession.clearCache();
第二种:两次查询之间执行了DML语句。【执行DML语句跟那张表没关系只要是DML语句执行了就会清空一级缓存】
二级缓存的范围是:SqlSessionFactory。
使用二级缓存需要具备以下四个条件:
全局性地开启所有映射器配置文件中已配置的任何缓存。默认就是true,开启状态,无需配置。
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
在需要使用二级缓存的SqlMapper.xml文件中添加配置:
使用二级缓存的实体类必须是可序列化的,也就是必须实现java.io.Serializable接口
SqlSession对象关闭或者提交之后,一级缓存中的数据才会被写入二级缓存。此时二级缓存才可以使用。
测试二级缓存:
SQL映射文件中添加cache标签
<cache>cache>
pojo类实现可序列化接口
public class Car implements Serializable {
//......
}
测试程序:
@Test
public void testSelectById2() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
// 关键一步,关闭了之后才会放到二级缓存当中
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmXQcPLD-1685102541207)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525205110785.png)]
二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】
二级缓存的相关配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lHVS6kWf-1685102541207)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525205215343.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>
第二步:在类的根路径下新建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"/>
第四步:编写测试程序使用。
@Test
public void testSelectById2() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XD7cFGE-1685102541207)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230525205619742.png)]
逆向工程就是:根据数据库表逆向生成java的pojo类,SqlMapper.xml文件,以及Mapper接口文件。
要完成这个工作,需要借助别人写好的逆向工程插件。
在Maven中插件和依赖都有gav,插件和依赖有什么区别和关系?
依赖(Dependencies)通常指的是项目运行时需要的外部库或框架,例如Apache Commons、Hibernate等。通过在pom.xml中配置依赖,Maven会自动下载相应的jar包并将其加入到项目的classpath中,以供编译和运行使用。依赖的GAV信息(groupId、artifactId、version)用来唯一标识一个依赖,Maven根据这些信息来确定依赖之间的关系和版本管理,以保证项目能够正确构建和运行。
插件(Plugins)则是用来扩展Maven构建过程的工具,通常用于执行某些特定的任务,例如编译代码、打包成jar/war文件、运行测试、生成文档等等。插件同样需要在pom.xml中进行配置,并且也有对应的GAV信息来标识。不同的插件可以在不同的生命周期阶段(如compile、test、package等)被调用执行,以达到构建目标的效果。
总体而言,依赖和插件都是通过GAV信息来进行标识和管理的,但二者的作用和影响范围是不同的。依赖用于程序运行时所需要的类库,而插件则扩展Maven构建过程的功能。同时,它们也有一定的关系,例如某些插件需要依赖特定的库才能正常运行,或者使用插件可以自动管理项目依赖等。
思考:使用这个插件的话,需要给这个插件配置哪些信息,插件才可以帮助我们逆向生成类?
新建模块:mybatis-011-generator
打包方式jar
<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>
如果没有设置允许覆盖则生成的就是这种情况:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DakbJCk2-1685102541208)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526150218610.png)]
该文件名是逆向工程插件的配置文件,该文件名必须叫做: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/powernode"
userId="root"
password="root">
jdbcConnection>
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
sqlMapGenerator>
<javaClientGenerator
type="xmlMapper"
targetPackage="com.powernode.mybatis.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
javaClientGenerator>
<table tableName="t_car" domainObjectName="Car"/>
context>
generatorConfiguration>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKDVH46J-1685102541208)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526143437600.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qMmJy2Ni-1685102541209)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526151216100.png)]
每个pojo类对应一个XXXExample类,是用来为Car添加复杂条件的。
@Test
public void test002 () {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
CarExample carExample = new CarExample();//创建条件对象
//添加类型中包含燃油车的模糊查询条件
carExample.createCriteria().andCarTypeLike("燃油车")
//添加指导价格在0到100之间的查询条件
.andGuidePriceBetween(new BigDecimal(0),new BigDecimal(100));
//添加或者关系的条件,以后添加的条件跟前面所欲的条件时或者的关系
//添加汽车品牌不为空的查询条件
carExample.or().andBrandIsNull();
System.out.println(carExample);
List<Car> cars = mapper.selectByExample(carExample);
for (Car car : cars) {
System.out.println(car);
}
sqlSession.close();
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQGnc1Z0-1685102541209)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526152228723.png)]
MySQL的limit后面两个数字
假设已知页码pageNum,还有煤业显示的记录条数pageSize,limit第一个数字可以动态计算:
startIndex = (pageNum - 1) * pageSize;
通用的MySQL分页sql
select
*
from
tableName ......
limit
(pageNum - 1) * pageSize, pageSize
使用MyBatis应该怎么做?
模块名:mybatis-012-page
映射器接口中的方法:
public interface CarMapper {
/**
* 通过分页的方式获取Car列表
* @param startIndex 页码
* @param pageSize 每页显示记录条数
* @return
*/
List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}
SQL映射文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectAllByPage" resultType="Car">
select * from t_car limit #{startIndex},#{pageSize}
select>
mapper>
测试程序:
@Test
public void testPage()throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 页码
Integer pageNum = 2;
// 每页显示记录条数
Integer pageSize = 3;
// 起始下标
Integer startIndex = (pageNum - 1) * pageSize;
List<Car> cars = mapper.selectAllByPage(startIndex, pageSize);
cars.forEach(car -> System.out.println(car));
sqlSession.commit();
sqlSession.close();
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNE0RYgq-1685102541210)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526165949546.png)]
获取数据不难。难的时获取分页相关的数据比较难。可以借助PageHelper插件。
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.1version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
在这段代码中,
元素是MyBatis配置文件中的一个标签,表示要配置的插件列表。
元素则表示一个具体的插件,并有一个名为interceptor
的属性,指定了该插件所使用的拦截器类。在这个例子中,com.github.pagehelper.PageInterceptor
就是PageHelper分页插件实际使用的拦截器类。
通过配置这个插件和拦截器,我们就可以在MyBatis中直接使用PageHelper提供的分页方法来进行分页查询,而无需手动编写繁琐的SQL语句。
拦截器有什么功能,在什么时候发挥作用?
com.github.pagehelper.PageInterceptor
是PageHelper提供的拦截器类,它的主要作用是在执行数据库操作之前对SQL语句进行拦截和处理。具体来说,它会解析SQL语句中的分页参数(如页码、每页行数等),并根据这些参数重新生成新的SQL语句。
当我们在MyBatis中调用查询方法时,PageInterceptor就会拦截这个查询操作,并将其转化为一个新的查询操作。在转化过程中,PageInterceptor会根据传入的分页参数生成相应的SQL语句,并设置合适的查询范围和排序方式。最终,PageInterceptor会将新的SQL语句返回给MyBatis框架,让其执行真正的查询操作。
通过使用PageInterceptor,我们可以在不修改原有SQL语句的情况下,实现分页查询功能。同时,PageInterceptor还支持多种数据库类型和分页模式,可以满足不同场景下的需求。
Mapper接口中的方法:
List<Car> selectAll();
SQL映射文件:
<select id="selectAll" resultType="Car">
select * from t_car
select>
在写SQL映射文件的时候,写完一条SQL语句一般后面不要加“;”,如果上述这条SQL语句加了“;”那么使用PageHelper的时候将会抛出异常:
在“;”后面又拼接了Limit语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-54FfYUO6-1685102541210)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526182324092.png)]
测试程序:
@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);
// 开启分页,拦截SQL语句,生成新的sql语句相对于原来的sql语句,后面增加了“LIMIT 2, 3”【简单理解,第一个参数时页码,第二个参数时每页显示多少条记录,在这里就是查询出来第二页的三条记录】
PageHelper.startPage(2, 3);
// 执行查询语句,一定要再执行查询语句之前开启分页功能,开启分页功能之后这里查询到的就不是所有的Car了,而是第二页的3条记录
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=6, pages=3, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3, reasonable=false, pageSizeZero=false}[Car{id=86, carNum=‘1234’, brand=‘丰田霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油车’}, Car{id=87, carNum=‘1234’, brand=‘丰田霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油车’}], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
对执行结果格式化:
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3, reasonable=false, pageSizeZero=false}
[Car{id=86, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'},
Car{id=87, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]
}
MyBatis中也提供了注解式开发,采用注解可以减少sql映射文件的配置。
使用注解的话sql语句可以写再Java程序中,这种方式也会给sql语句维护成本降低。
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
使用注解编写复杂的SQL是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeP0OLBL-1685102541211)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526194109371.png)]
虽然也能编写但是写出来的sql让然看起来复杂的不能看。
原则:简答sql可以注解,复杂sql使用xml。
//接口中的方法
public interface CarMapper {
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
}
//测试程序
@Test
public void testInsert() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "1112", "卡罗拉", 30.0, "2000-10-10", "燃油车");
int count = mapper.insert(car);
System.out.println("插入了几条记录:" + count);
sqlSession.commit();
sqlSession.close();
}
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
@Test
public void testDelete() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
mapper.deleteById(89L);
sqlSession.commit();
sqlSession.close();
}
@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);
@Test
public void testUpdate() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(88L,"1001", "凯美瑞", 30.0,"2000-11-11", "新能源");
mapper.update(car);
sqlSession.commit();
sqlSession.close();
}
@Select("select * from t_car where id = #{id}")
//使用@Results注解配置结果映射
@Results({
@Result(column = "id", property = "id", id = true), //id = true代表id是主键
@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);
@Test
public void testSelectById() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper carMapper = sqlSession.getMapper(CarMapper.class);
Car car = carMapper.selectById(88L);
System.out.println(car);
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rYmwyMJ-1685102541211)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230526194448260.png)]
L语句发送给DBMS。
2. 在查询语句之后封装PageInfo对象。【如果时web应用可以将pageInfo对象存储到request域当中。在页面上展示。】
2. PageInfo类构造方法的参数:
执行结果:
PageInfo{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3, reasonable=false, pageSizeZero=false}[Car{id=86, carNum=‘1234’, brand=‘丰田霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油车’}, Car{id=87, carNum=‘1234’, brand=‘丰田霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油车’}], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
对执行结果格式化:
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3, reasonable=false, pageSizeZero=false}
[Car{id=86, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'},
Car{id=87, carNum='1234', brand='丰田霸道', guidePrice=50.5, produceTime='2020-10-11', carType='燃油车'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]
}
MyBatis中也提供了注解式开发,采用注解可以减少sql映射文件的配置。
使用注解的话sql语句可以写再Java程序中,这种方式也会给sql语句维护成本降低。
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
使用注解编写复杂的SQL是这样的:
[外链图片转存中…(img-IeP0OLBL-1685102541211)]
虽然也能编写但是写出来的sql让然看起来复杂的不能看。
原则:简答sql可以注解,复杂sql使用xml。
//接口中的方法
public interface CarMapper {
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
}
//测试程序
@Test
public void testInsert() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "1112", "卡罗拉", 30.0, "2000-10-10", "燃油车");
int count = mapper.insert(car);
System.out.println("插入了几条记录:" + count);
sqlSession.commit();
sqlSession.close();
}
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
@Test
public void testDelete() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
mapper.deleteById(89L);
sqlSession.commit();
sqlSession.close();
}
@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);
@Test
public void testUpdate() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(88L,"1001", "凯美瑞", 30.0,"2000-11-11", "新能源");
mapper.update(car);
sqlSession.commit();
sqlSession.close();
}
@Select("select * from t_car where id = #{id}")
//使用@Results注解配置结果映射
@Results({
@Result(column = "id", property = "id", id = true), //id = true代表id是主键
@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);
@Test
public void testSelectById() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper carMapper = sqlSession.getMapper(CarMapper.class);
Car car = carMapper.selectById(88L);
System.out.println(car);
}
执行结果:
[外链图片转存中…(img-8rYmwyMJ-1685102541211)]