Mybatis理解
什么是Mybatis?
- Mybatis是对JDBC的封装,里面含有大量的class
- Mybatis在三层架构中负责持久层,属于持久层框架
- 框架其实就是对通用代码的封装,提前写好了一堆接口和类,我们可以在做项目的时候直接引入这些接口和类(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率。
- 支持定制化 SQL、存储过程、基本映射以及高级映射
- 避免了几乎所有的 JDBC 代码中手动设置参数以及获取结果集
- 支持XML开发,也支持注解式开发。为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。
- 将接口和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录
ORM是什么?
- ORM是一种映射关系,具体表现在 在关系型数据库跟业务实体对象之间作一个映射
为什么要用Mybatis?JDBC不好嘛?
String sql = "select id,idCard,username,password,birth,gender,email,city,street,zipcode,phone,grade from t_user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
List<User> userList = new ArrayList<>();
while(rs.next()){
String id = rs.getString("id");
String idCard = rs.getString("idCard");
String username = rs.getString("username");
String password = rs.getString("password");
User user = new User();
user.setId(id);
user.setIdCard(idCard);
user.setUsername(username);
user.setPassword(password);
}
以上代码有大量重复的代码,而且sql语句写死在java程序当中,一旦需要写别的sql,就要反复重复上面的代码
所以可以使用Mybatis框架
开发步骤
-
打包方式jar
-
引入依赖
- mybatis依赖
- mysql驱动依赖
-
编写mybaits核心配置文件:mybatis-config.xml
-
这个而文件名不是必须叫做mybatis-config.xml,可以用其他名字,但一般用这个
-
这个文件存放的位置也不是固定的,可以随便,但一般情况下会放到类的根路径下
-
<mappers> <!--指定XxxMapper.xml文件的路径,resource是从类路径下开始加载资源--> <mapper resource="CarMapper.xml"></mapper> <!--url属性,从绝对路径当中加载资源--> <mapper url="file:///d:xxxx.xml"></mapper> </mappers> //事务管理机制,type有两个值:JDBC,MANAGED //JDBC:mybatis自己用JDBC的事务方法, conn.seAutoCommit(false),conn.commit(); //MANAGEN:mybatis不负责事务,事务管理交给其他容器来管理,例如:Sping <transactionManager type="JDBC"></transactionManager>
-
//mybatis事务 SqlSession sqlSession = sqlSessionFactory.openSession(); //如果事务管理器是JDBC,底层实际上执行了:conn.setAutoCommit(false),开启事务 System.out.println(sqlSession.getConnection().getAutoCommit());//false //sqlSession.commit(); //JDBC:如果不提交,数据库数据不会更新,因为开启了事务,关闭了自动提交 //表示没有提交事务,因为开启了自动提交,底层没有执行conn.setAutoCommit(false) //那么autoCommit就是true,每执行一次DML语句就提交一次 //所以下面代码不建议 SqlSession sqlSession = sqlSessionFactory.openSession(true); //如果事务管理器是MANAGEN,交给别的容器管理事务,如果没有别的容器的情况下,等于说没人管理事务,等于说没有事务,等于说不开启事务,等于说开启自动提交 SqlSession sqlSession = sqlSessionFactory.openSession();
-
-
编写XxxMapper.xml文件
- 在这个文件中编写sql语句
-
在mybatis-config.xml文件中指定XxxMapper.xml文件的路径:
<mapper resource="CarMapper.xml"/>
- resource属性会自动从类的根路径下开始查找资源
-
开始编写mybatis程序
在Mybatis中负责执行sql语句的对象叫 sqlSession
sqlSession是专门用来执行sql,是一个java程序和数据库之间的一次会话
怎么获取SQLSessionFactory对象?
-
首先需要先获取SqlSessionFactoryBuilder对象
-
通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象
-
SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession
从xml中构建sqlSessionFactory
mybatis中有两个重要的配置文件
- 一个是mybatis-config.xml,这个是核心配置文件,主要配置连接数据库的信息
- 还有一个是XxxMapper.xml,这个文件专门用来编写sql语句的配置(一个表一个)
事务机制的深度理解
//mybatis事务
SqlSession sqlSession = sqlSessionFactory.openSession();
//如果事务管理器是JDBC,底层实际上执行了:conn.setAutoCommit(false),开启事务
System.out.println(sqlSession.getConnection().getAutoCommit());//false
//sqlSession.commit();
//JDBC:如果不提交,数据库数据不会更新,因为开启了事务,关闭了自动提交
//表示没有提交事务,因为开启了自动提交,底层没有执行conn.setAutoCommit(false)
//那么autoCommit就是true,每执行一次DML语句就提交一次
//所以下面代码不建议
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//如果事务管理器是MANAGEN,交给别的容器管理事务,如果没有别的容器的情况下,等于说没人管理事务,等于说没有事务,等于说不开启事务,等于说开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession();
单元测试
-
命名规范:测试类名 + Test
-
一个业务方法对应一个测试方式
-
测试方法:public void testXxxx(){}
-
测试方法的方法名:以test开始,假设测试的方法是sum,这个测试方法名:testSum
-
@Test注释非常重要,被这个注释标注的方法就是一个单元测试方法
-
public class MathService { public int sum(int a,int b ){ return a+b; } public int sub(int a,int b ){ return a-b; } }
-
public class MathServiceTest { @Test public void testSum(){ MathService mathService = new MathService(); int res = mathService.sum(1, 2); int expected = 3; Assert.assertEquals(expected,res); } @Test public void testSub(){ MathService mathService = new MathService(); int res = mathService.sub(10, 1); int expected = 3; //断言 Assert.assertEquals(expected,res); } }
MyBatis日志框架
增删改查(CRUD)
在MyBatis中占位符不是?,是#{}
增删改同理
在CarMapper.xml中
<insert id="ins">
insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
</insert>
在java程序中使用map集合给sql传值
public static void main(String[] args) {
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
Map<String,Object> map = new HashMap<>();
map.put("car_num","111");
map.put("brand","比亚迪");
map.put("guide_price","570");
map.put("produce_time","2001-5-7");
map.put("car_type","电机");
int cout = sqlSession.insert("ins",map);
sqlSession.commit();
}
实际上: 底层实际上是调用了map.get()去读值
用javaBean给sql传值
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
//还有getter,setter方法
}
public void testInsertCarByBean(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
Car car = new Car(null, "2121", "保时捷2", 75.1, "2021-8-7", "新能源");
sqlSession.insert("ins",car);
sqlSession.commit();
sqlSession.close();
}
- insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
实际上:底层上其实是去找了javabean里面的get方法,通过guidePrice,carNum这些占位符,只是将G,C大写,找的是getGuidePrice,getCarNum
查询一个(selectOne)
resultType
查询结果一定会返回,跟JDBC的结果集一样,需要指定结果集对象是什么类型的(resultType)
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
Object car = sqlSession.selectOne("selectById", 1);
System.out.println(car);//结果自动调用car.toString()
sqlSession.close();
}
结果集对象类型(resultType)指定为Car (javaBean) ,需要指定全限定类名
<select id="selectById" resultType="com.aw.mybatis.bean.Car">
select * from t_car where id = #{id};
</select>
结果出现问题:Car{id=1, carNum=‘null’, brand=‘宝马’, guidePrice=null, produceTime=‘null’, carTyppe=‘null’}
只有id,brand有值,其他都是null
数据库的值是:
原因:列名跟属性名对不上,javaBean中的属性名是:carNum,数据库里的列名是:car_num
第一种解决办法:让列名跟属性名对应
<select id="selectById" resultType="com.aw.mybatis.bean.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>
resultMap
第二种解决办法
不用再写别名了,单独定义一个结果映射,在这个结果映射中指定数据库表的字段名和java类的属性名的对应关系
- id:指定resultMap的唯一标识,这个id将来要在select标签中使用
- type:用来指定bean类的类名
- 在result标签中,如果有主键要写id作为主键,即:,可以提升检索效率
- property是bean类的属性名
- column是数据库字段名
<resultMap id="carResultMap" type="Car">
<id property="id" column="id"/>
<!-- property是bean类的属性名,column是数据库字段名-->
<result property="carNum" column="car_num"/>
<result property="brand" column="brand"/>
<result property="carType" column="car_type"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
</resultMap>
<select id="selectAllByResultMap" resultMap="carResultMap">
select * from t_car
</select>
开启驼峰自动映射
第三种解决办法
在mybatis-config.xml中
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
查询所有
<select id="selectAll" resultType="com.aw.mybatis.bean.Car">
select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
from t_car
</select>
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
List<Car> cars = sqlSession.selectList("selectAll");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
命名空间的使用(namespace)
当存在多个xml时,有时候sql的id会重复,这样mybatis不知道执行哪个sql,可以使用namespace.id,防止id冲突
<mapper namespace="aaa">
<insert id="ins">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
</insert>
</mapper>
<mapper namespace="org.mybatis.example.BlogMapper">
<insert id="ins">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
</insert>
</mapper>
@Test
public void testInsertCarByBean(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
Car car = new Car(null, "2121", "保时捷2", 75.1, "2021-8-7", "新能源");
sqlSession.insert("aaa.ins",car);
sqlSession.commit();
sqlSession.close();
}
配置文件
环境(environments)
环境environment
<!-- default表示默认使用的环境-->
<!-- 默认环境的意思是:当使用mybatis创建SqlSessionFactory对象的时候,如果没有指定环境的话,会使用默认环境-->
<environments default="mybatis_demo">
<!-- 其中的一个环境,连接的数据库是mybatis_demo-->
<environment id="mybatis_demo">
<!--type有两个值,一个是JDBC,一个是MANAGED-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!-- mybatis的另一个环境,也就是连接的数据库是另一个数据库test0518-->
<!-- 一个环境environment会对应一个sqlSessionFactory对象-->
<environment id="test0518">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test0518"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
案例理解
数据库信息:
- 在mybatis_demo中t_car : 字段信息: 1 100 宝马 41 2022-07-1 燃油车
- 在test0518中user:字段信息:测试 20 男
<mapper namespace="aaa">
<select id="selectById" resultType="java.lang.Integer">
select
age
from user
where name = #{name};
</select>
</mapper>
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectById" resultType="com.aw.mybatis.bean.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>
</mapper>
@Test
public void testEnvironment() throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//采用默认环境
SqlSessionFactory factory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = factory1.openSession();
Object car = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectById", 1);
System.out.println(car);//Car{id=1, carNum='100', brand='宝马520Li', guidePrice=41.0, produceTime='2022-09-1', carTyppe='燃油车'}
//指定环境
SqlSessionFactory factory2 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "test0518");
SqlSession sqlSession2 = factory2.openSession();
Object car2 = sqlSession2.selectOne("aaa.selectById", "测试");
System.out.println(car2); //20
}
事务管理器transactionManager
<transactionManager type="JDBC"></transactionManager>
type有两个值,一个是JDBC,一个是MANAGED
mybatis提供了一个是事务管理器接口:Transaction
这个接口有两个实现类,一个是JDBC的JdbcTransaction,一个是MANAGED的ManagedTransaction
如果type=“JDBC”,就实例化JdbcTransaction
如果type=“MANAGED”,就会实例化ManagedTransaction
数据源dataSource
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
数据源作用是什么?
-
为程序员提供Connection对象(凡是给程序员提供Connection对象的,都叫做数据源)
-
数据源实际上是一套规范,JDK中有这套规范:javax.sql.DataSource
-
可以自己写数据源,只要实现DataSource接口就行了
-
常见的数据远组件有什么?(连接池)
- 阿里巴巴德鲁伊连接池:druid
- c3p0
- dbcp
-
type属性用来指定数据源的类型,就是指定具体用什么方式来获取Connection对象
-
type有三个值:UNPOOLED | POOLED | JNDI
- UNPOOLED:不使用数据库连接池技术,每次请求都创建新的Connection对象
- POOLED:使用mybatis自己实现的数据库连接池
- JNDI:集成其他第三方的数据库连接池
- JNDI是一套规范,大部分web都是先了JNDI规范:Tomcat,Jetty,WebLogic,WebSphere
- JNDI:java命名目录规范
设置(settings)
<settings>
<setting name="logImpl" value="slf4j"/>
<setting name="lazyLoadingEnabled" value="true "/>开始全局懒加载
<setting name="mapUnderscoreToCamelCase" value="true"/>驼峰映射
</settings>
别名(typeAliases)
<!--
起别名
type:指定给哪个类型起别名
alias:指定别名
-->
<typeAliases>
<typeAlias type="com.aw.bean.Car" alias="Car"/>
<typeAlias type="com.aw.bean.Log" alias="Log"/>
</typeAliases>
在xml中:
<select id="selectById" resultType="car">
<!--<select id="selectById" resultType="com.aw.mybatis.bean.Car">-->
<!--<select id="selectById" resultType="com.aw.mybatis.bean.cAr">-->
select id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type ascarType
from t_car
where id=#{id}
</select>
注意!
namespace
只能用全限定名
resultType
中可以写别名,且不区分大小写
alias
可以省略不写,因为会默认使用type中的类名
但是每个bean都要写一遍typeAlias
很麻烦,所以引入了用包名的方式,只要在包下,所有的类都自动起别名,别名就是类简名,且不区分大小写(意思就是大小写无所谓)
<typeAliases>
<package name="com.aw.mybatis.bean"/>
</typeAliases>
映射器(Mappers)
等同于说是注册
<mappers>
<mapper resource="CarMapper.xml"></mapper>
<mapper resource="LogMapper.xml"></mapper>
</mappers>
maper有三个属性:resource,url,class
- resource:从类的根路径下开始查找资源,
- url:绝对路径
- class:提供mapper接口的全限定接口名,需要带有包名
- 提供:
- mybatis会自动去:com.aw.mybatis.mapper下找CarMapper.xml文件
- 所以使用class这种方式,需要将xml文件放到接口的同一包下
- 可以在resources建一个一样的目录,将xml写在mapper下,这样生成之后也是在同一目录下
同样的,假如有100mapper,就要手动注册100个,很麻烦,跟typeAliases一样,提供了package的方式
<mappers>
<package name="com.aw.mybatis.mapper"/>
</mappers>
动态代理
Mybatis采用了动态代理的机制,让程序员不用自己写Dao的实现类
但是是有要求的:
-
映射器中必须是全限定类名
-
为什么?因为在底层代理机制中,通过类名+SqlId的形式获取到sql语句
-
public Account selectByName(String name) { SqlSession sqlSession = SqlSessionUtil.openSqlSession(); return (Account) sqlSession.selectOne("account.selectByName", name); }
-
如果不指定,很难获取sql,
account.selectByName
,因为这个是用户自己定的,比如: -
<!--<mapper namespace="com.aw.bank.dao.AccountDao">--> <mapper namespace="xxx"> <select id="aa" resultType="com.aw.bank.bean.Account"> select * from bank where name=#{name} </select> <update id="bb" > update bank set balance=#{balance} where name=#{name} </update> </mapper>
-
使用动态代理机制,可以只通过Dao接口实现类并且创建对象
以下是实现原理:
package com.aw.bank.utils;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
/**
* @ClassName GenerateDaoProxy
* @Author aw
* @Date 2022/10/30 16:46
* @Version 1.0
* @Description 工具类,动态生成DAO的实现类
**/
public class GenerateDaoProxy {
public static Object generate(SqlSession sqlSession, Class doInterface) {
//类池
ClassPool pool = ClassPool.getDefault();
//制造类,指定类
//com.aw.bank.dao.AccountDao ->com.aw.bank.dao.AccountDaoImpl
CtClass ctClass = pool.makeClass(doInterface.getName() + "Impl");
//制作实现类的接口
CtClass ctInterface = pool.makeInterface(doInterface.getName());
//实现接口(添加继承关系)
ctClass.addInterface(ctInterface);
//实现接口的方法
Method[] methods = doInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
//将method实现
try {
StringBuilder methodCode = new StringBuilder();
methodCode.append("public ");
methodCode.append(method.getReturnType().getName() + " ");
methodCode.append(method.getName());
methodCode.append("(");
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getName() + " ");
methodCode.append("arg" + i);
if ( i !=parameterTypes.length-1){
methodCode.append(",");
}
}
methodCode.append("){");
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = org.apache.ibatis.session.SqlSessionUtil.openSqlSession();");
// methodCode.append("sqlSession.");
//需要知道是什么类型的sql语句
String sqlId = doInterface.getName() +"." + method.getName();
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if (sqlCommandType == SqlCommandType.DELETE) {
}
if (sqlCommandType ==SqlCommandType.INSERT){
}
if (sqlCommandType == SqlCommandType.SELECT) {
System.out.println(method.getReturnType().getName());
methodCode.append("return ("+method.getReturnType().getName() +") sqlSession.selectOne(\""+sqlId+"\",arg0);");
}
if (sqlCommandType == SqlCommandType.UPDATE) {
methodCode.append("return sqlSession.update(\""+sqlId+"\",arg0);");
}
methodCode.append("}");
System.out.println(methodCode);
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
Object obj =null;
try {
Class<?> aClass = ctClass.toClass();
obj = aClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
Mybatis中提供了一个方法可以直接达到代理功能:
private AccountDao accountDao = SqlSessionUtil.openSqlSession().getMapper(AccountDao.class);
等同于:
SqlSession sqlSession = SqlSession.openSqlSession();
XxxMapper xxxMapper = sqlSession.getMapper(XxxMapper.class)
所以!总结一下:代理机制,跟我们自己写DaoImpl实现类有什么区别?!
我们自己写的:
//bean
//实现类
//实现类的方法
public Account selectByName(String name) {
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
return (Account) sqlSession.selectOne("account.selectByName", name);
}
//new实现类
private AccountDao accountDao = new AccountDaoImpl();
//调用实现类方法
Account toAct = accountDao.selectByName(toActName);
代理机制:
//bean
//代理机制创建对象
private AccountDao accountDao =(AccountDao)GenerateDaoProxy.generate(SqlSessionUtil.openSqlSession(),AccountDao.class);
//调用方法
Account toAct = accountDao.selectByName(toActName);
等于说我们省了一步创建类对象,以及实现类方法
我们可以直接通过代理机制创建的对象调用方法
我们自己写是一样的,只是更麻烦一些,要实现类
${}跟#{}的区别
-
#{}
-
<select id="selectByType" resultType="com.aw.mybatis.bean.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime from t_car where car_type=#{carType} </select>
-
执行结果:
-
Car{id=2, carNum='46', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='null'} Car{id=27, carNum='46', brand='大众', guidePrice=4.0, produceTime='2022-8-5', carTyppe='null'}
-
${}
-
<select id="selectByType" resultType="com.aw.mybatis.bean.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime from t_car where car_type=${carType} </select>
-
执行结果:
-
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'carType' in 'class java.lang.String' ### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'carType' in 'class java.lang.String'
#{}:底层使用PreparedStatment,特点:先进行SQL语句的编译,然后给SQL语句的占位符?传值
${}:底层使用Statement,特点:先进行SQL语句的拼接,然后在对SQL语句进行编译
使用场景:
-
当需要传值的时候,比如:where id=“张三”,这个时候就要用#{},因为使用了占位符?,传值的时候传的是字符串,带双引号“ ”,使用#{}
-
不需要传值的时候,
- 比如:order by xxx desc,当中的desc需要用户指定,但是又不是字符串,不需要双引号,使用${}
- 有些时候需要拼串的时候:tb_xxx,需要指定表,而这个xxx需要用户传过来,就用${}
记住一点!
**使用#{}的时候,sql语句会有双引号“”,使用${},sql语句没有双引号 **
注意:
-
当使用 的时候,参数名称必须是 {}的时候,参数名称必须是 的时候,参数名称必须是{value}或者${_parameter},否则会有找不到参数的错误
-
org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'time' in 'class java.lang.String'
-
<select id="selectAll" resultType="com.aw.mybatis.bean.Log"> select * from t_log${value} <!-- select * from t_log${time} 会找不到time参数--> </select>
-
但是使用#{}的时候,可以随便写参数名称
-
<delete id="deleteById"> delete from t_car where id=#{id} <!--where id=#{aaa} 可以--> <!--where id=#{bbb} 可以--> </delete>
模糊查询
-
使用:‘%${}%’
-
select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type like '%${value}%'
-
sql: where car_type like ‘%能源%’
-
-
使用
concat
函数-
select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type like concat('%',#{carType},'%')
-
sql: where car_type like concat(‘%’,?,‘%’)
-
mapper属性
useGeneratedKeys="true"表示使用自动生成的主键值,
keyProperty="id"表示将主键值赋值给对象的哪个属性,这个表示将主键值赋值给Car对象的id
<insert id="insertCarUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
Mybatis多参数处理
parameterType属性的作用:告诉mybatis,这个方法的参数类型是什么类型的
List<Stu> selectByNameAndSex(String name,String sex);
<select id="selectByNameAndSex" resultType="Stu">
select * from t_stu where name=#{name} and sex=#{sex}
</select>
//报错
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]
当有多个参数的时候,mybatis会自动创建一个Map集合,且集合以这种方式存值:
map.put("arg0",name)
map.put("arg1",sex)
map.put("param1",name)
map.put("param2",sex)
都在同一个Map集合里
<select id="selectByNameAndSex" resultType="Stu">
select * from t_stu where name=#{arg0} and sex=#{arg1}
</select>
运行成功:
stu{id=1, name='张三', age=50, height=1.81, brith=Mon Aug 15 00:00:00 CST 2022, sex='男'}
底层发现多参数的时候,判断参数有没有@param注解,如果没有就自动创建集合并赋值arg,param
List<Stu> selectByNameAndSex2(@Param("name") String name, @Param("sex") String sex);
底层现在创建Map集合的时候就是:
map.put("name",name)
map.put("sex",sex)
底层代理过程
只做了解
Map返回结果
如果执行结果没有合适的java对象来接收,可以使用map来接收
Map<String,Object> selectByIdRetMap(Long id);
<select id="selectByIdRetMap" resultType="map">
select * from t_car where id=#{id}
</select>
@Test
public void TestSelectByIdRetMap(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Map<String,Object> car = mapper.selectByIdRetMap(22L);
System.out.println(car);
}
返回结果:
{car_num=46, id=22, guide_price=4, produce_time=2022-8-7, brand=法拉利, car_type=燃油}
以key,value的形式存在:key是数据库字段名,value是值
ListMap返回结果
如果执行结果没有合适的java对象来接收,且有多条记录,可以用ListMpa来接收
List<Map<String ,Object>> selectAllRetListMap();
<select id="selectAllRetListMap" resultType="map">
select * from t_car
</select>
@Test
public void TestSelectAllRetListMap(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Map<String, Object>> mapList = mapper.selectAllRetListMap();
mapList.forEach(map->{System.out.println(map);});
sqlSession.close();
}
返回结果:
{car_num=47, id=2, guide_price=4, produce_time=2022-8-5, brand=迈凯伦, car_type=新能源}
{car_num=46, id=22, guide_price=4, produce_time=2022-8-7, brand=法拉利, car_type=燃油}
{car_num=48, id=23, guide_price=3, produce_time=2022-8-31, brand=劳斯莱斯, car_type=电气}
{car_num=49, id=24, guide_price=4, produce_time=2022-7-5, brand=劳斯莱斯幻影, car_type=电气}
{car_num=50, id=26, guide_price=4, produce_time=2025-8-5, brand=奥迪, car_type=燃油}
但是有个缺点,取的时候要一个一个遍历,判断是不是自己想要的才取出来,检索效率低
MapMap返回结果
如果执行结果没有合适的java对象来接收,且有多条记录,可以用MapMpa来接收
@MapKey(“id”) 需要指定大map中什么字段作为key,一般选择主键
@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetBigMap();
<select id="selectAllRetBigMap" resultType="map">
select * from t_car
</select>
@Test
public void TestSelectAllRetBigMap(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Map<Long, Map<String, Object>> longMapMap = mapper.selectAllRetBigMap();
System.out.println(longMapMap);
sqlSession.close();
}
返回结果:
{
32={car_num=50, id=32, guide_price=5, produce_time=2022-8-9, brand=小鹏, car_type=电气},
33={car_num=50, id=33, guide_price=5, produce_time=2022-8-9, brand=小鹏, car_type=电气}
}
动态SQL
if
在sql中可以通过标签动态的指定是否拼接相应的内容
<!--
if标签中test是必须的,test的返回值值是bool,如果是true就拼接内容,false就不拼接
当使用了@Param,那么test中的是@Param指定的参数名:@Param(“xx)-> test="xx....."
如果没有使用@Param,那么test中要出现的是param1,param2.....
如果使用了bean,那么test中出现的是bean的属性名
-->
例如:
List<Car> selectByBdGpCt (@Param("brand")String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByBdGpCt" resultType="Car">
select * from t_car where 1=1
<if test="brand !=null and brand!= ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice !=null and guidePrice !=''">
and guide_price >#{guidePrice}
</if>
<if test="carType !=null and carType !=''">
and car_type =#{carType}
</if>
</select>
@Test
public void testSelectByBdGpCt(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// List<Car> cars = mapper.selectByBdGpCt("雷克萨斯", 3L, "新能源");
// List<Car> cars = mapper.selectByBdGpCt("", 0, "");
List<Car> cars = mapper.selectByBdGpCt("雷克萨斯", null, "");
cars.forEach(car -> System.out.println(car));
}
那么问题来了,在sql中那么多变量,一会是sql的字段,一会是bean属性,一会又是@Param指定的值,怎么区分呢?
List<Car> selectByBdGpCt(@Param("a")String brand, @Param("b") Double guidePrice, @Param("c") String carType);
<select id="selectByBdGpCt" resultType="Car">
select * from t_car where 1=1
<if test="a !=null and a!= ''">
and brand like "%"#{a}"%"
</if>
<if test="b !=null and b !=''">
and guide_ price >#{b}
</if>
<if test="c !=null and c !=''">
and car_type =#{c}
</if>
</select>
这样写只是为了理解区分值的含义
通过上面的例子看出:
-
如果有@Param注解
- test里面就用注解值,#{}接收值也是注解值,为什么?
- 因为用了注解之后,会生成map,map的key不就是注解值吗,value是传入的参数值
-
如果没有@Param注解
-
如果是只有一个参数:
- test里面要用value接收,#{}也是用value接收
-
如果有多个参数
- test里面用param1,param2…接收值,#{}接收值也是用param1…
-
-
如果参数传入的是一个bean对象:(User user)
- test里面用bean对象属性值接收值,#{}接收值也是用bean对象属性值
注意!
-
如果只传入一个参数并且是String类型,并且需要用标签时,直接写参数是不行的:
<select id="selectByCt" resultType="Car"> select * from t_car where 1=1 <if test="carType !=null and carType !=''"> and car_type =#{carType} </if> </select>
原因未知
-
解决办法:使用value
<select id="selectByCt" resultType="Car"> select * from t_car where 1=1 <if test="value !=null and value !=''"> and car_type =#{value} </if> </select>
where
-
所有条件都为空的时候,sql不会生成where语句
-
自动去掉某些条件前面多余的and 或者 or
<select id="selectByBdGpCtWithWhere" resultType="Car">
select * from t_car
<where>
<if test="brand !=null and brand!= ''">
brand like "%"#{brand}"%"
</if>
<if test="guidePrice !=null and guidePrice !=''">
guide_price >#{guidePrice}
</if>
<if test="carType !=null and carType !=''">
car_type =#{carType}
</if>
</where>
</select>
tirm
- prefix: 加前缀
- suffix: 加后缀
- prefixOverrides: 删除前缀
- suffixOverrides: 删除后缀
<select id="selectByBdGpCtWithTrim" resultType="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} or
</if>
</trim>
</select>
结果:select * from t_car where brand like "%"?"%" and guide_price >? and car_type =?
set
判断条件里是否为true,如果为true就执行这个set,false就不执行,并且能够自动删除多余的","
用于update的set,正常更新操作是:
<update id="updateById">
update t_car
car_num=#{carNum},
test="brand !=null and brand !=''"> brand=#{brand},
guide_price=#{guidePrice},
product_time=#{produceTime},
car_type=#{carType},
where
id=#{id}
</update>
需要创建对象new xx()然后传值,如果传的值是null,那么原来有的值也会变成null,不够灵活
如果需要做到:我只修改我想修改的值,其他的值不变
<update id="updateById">
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 !=''"> product_time=#{produceTime},</if>
<if test="carType !=null and carType !=''"> car_type=#{carType},</if>
</set>
where
id=#{id}
</update>
Car car = new Car(34L,null,"库利南",null,null,"旧能源");
int count= mapper.updateById(car);
sql:update t_car SET brand=?, car_type=? where id=?
,只修改想修改的部分
choose when otherwise
类似:if elseif,只会执行其中一条语句
<select id="selectByChoose" resultType="Car">
select * from t_car
<where>
<choose>
<when test="brand !=null and brand!= ''">
brand like "%"#{brand}"%"
</when>
<when test="guidePrice !=null and guidePrice !=''">
guide_price >#{guidePrice}
</when>
<otherwise>
car_type =#{carType}
</otherwise>
</choose>
</where>
</select>
@Test
public void testSelectByChoose(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByChoose("雷克萨斯", 3.0, "新能源");
cars.forEach(car -> System.out.println(car));
} //select * from t_car WHERE brand like "%"?"%"
@Test
public void testSelectByChoose(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByChoose("", null, "新能源");
cars.forEach(car -> System.out.println(car));
}//select * from t_car WHERE car_type =?
@Test
public void testSelectByChoose(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByChoose("", null, "");
cars.forEach(car -> System.out.println(car));
} //select * from t_car WHERE car_type =? 其中?=null
从结果不难看出:
- 如果存在多个true的,也只会执行第一个,因为在中只执行一条或者
- 如果中没有true,一定会执行,大不了就找不到
foreach
遍历数组或者集合,获取元素
- collection:指定数组或者集合
- item:数组或者集合中的元素
- separator:循 环之间的分隔符
- open:for循环执行开始
- close:for循环执行结束
<delete id="deleteByIds" >
<!-- delete from t_car where id in(-->
<!-- <foreach collection="array" item="id" separator=",">-->
<!-- #{id}-->
<!-- </foreach>-->
<!-- )-->
delete from t_car where id in(
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)
</delete>
等同于:
<delete id="deleteByIds" >
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(",close=")">
#{id}
</foreach>
</delete>
int deleteByIds(@Param("ids") Long[] ids);
sql跟inclued
sql:声明sql片段
iniclued:使用sql片段
<sql id="select*">
select * from t_car
</sql>
<select id="selectByBdGpCt" resultType="Car">
<include refid="select*"/>
where 1=1
<if test="brand !=null and brand!= ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice !=null and guidePrice !=''">
and guide_price >#{guidePrice}
</if>
<if test="carType !=null and carType !=''">
and car_type =#{carType}
</if>
</select>
目的是减少重复代码,提高代码复用率
Mybatis高级映射
多对一
多个学生对应一个班级
多种映射方式:常见的包括三种
- 一条sql语句,级联映射
- 一条sql语句,association
- 两条sql语句,分步查询(常用,优点是可复用,支持懒加载
级联映射
在resultMap中指定传出值
<resultMap id="studentResultMap" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_class c on s.cid=c.cid where s.sid=#{id}
sql中查询结果为:sid,sname,cid,cname
而在resultMap中指定:
- sid值传给Type为Stu中的属性sid
- sname值传给Type为Stu中的属性sname
- cid值传给Type为Stu中属性为clazz对象中的cid
- clazz是对象,对象里也有clazz自己的属性
- Stu对应着t_stu表,Clazz对应着t_class表,而stu里包含clazz属性说明:
- 多对一的关系,多个学生对应一个班级,一个班级对应多个学生
- 每个学生只有一个班级,对应到java实体关系上:Stu中含有clazz属性
案例
根据id获取学生信息,同时获取学生关联的班级信息
package com.aw.mybatis.bean;
/**
* @ClassName Stu
* @Author aw
* @Date 2022/11/3 23:08
* @Version 1.0
* @Description 多对1,Stu是多的一方,Clazz是一的一方,代表
**/
public class Stu {
private Integer sid;
private String sname;
private Clazz clazz;
//所有属性的get,set,toString,构造方法此处忽略不写(节省位置)实际要写
//Clazz类里面有cid,cname属性
}
public interface StuMapper {
/*
* @Author aW
* @Description 根据id获取学生信息,同时获取学生关联的班级信息
* @Date 0:03 2022/11/4
* @Param [id] 学生id
* @return com.aw.mybatis.bean.Stu 学生对象,但是学生对象中含有班级对象
**/
Stu selectById(Integer id);
}
<resultMap id="studentResultMap" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select
s.sid,s.sname,c.cid,c.cname
from
t_stu s left join t_class c on s.cid=c.cid
where
s.sid=#{id}
</select>
public class StuMapperTest {
@Test
public void TestSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
Stu stu = mapper.selectById(1);
System.out.println(stu);
System.out.println(stu.getSid());
System.out.println(stu.getSname());
System.out.println(stu.getClazz().getCid());
System.out.println(stu.getClazz().getCname());
}
}
//输出结果
//Stu{sid=1, sname='张三', clazz=Class{cid=1001, cname='高三1班'}}
//1 张三 1001 高三1班
association
<resultMap id="studentResultMapByAssociation" type="Stu">
<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>
select s.sid,s.sname,c.cid,c.cname from t_stu s left join t_class c on s.cid=c.cid where s.sid=#{id}
sql中查询结果为:sid,sname,cid,cname
而在resultMap中指定:
- sid值传给Type为Stu中的属性sid
- sname值传给Type为Stu中的属性sname
- association说明clazz属性是一个对象,类似于bean对象里套bean对象
- cid值传给Stu中属性为clazz中的属性cid
- cname值传给Stu中属性为clazz中的属性cname
案例
根据id获取学生信息,同时获取学生关联的班级信息
Stu selectByAssociation(Integer id);
<resultMap id="studentResultMapByAssociation" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<!-- association:一个bean对象关联另一个对象
property:bean类中的属性,
javaType:用来指定要映射的java类型-->
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
</association>
</resultMap>
<select id="selectByAssociation" resultMap="studentResultMapByAssociation">
select
s.sid,s.sname,c.cid,c.cname
from
t_stu s left join t_class c on s.cid=c.cid
where
s.sid=#{id}
</select>
@Test
public void TestSelectByAssociation(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
Stu stu = mapper.selectByAssociation(1);
System.out.println(stu);
}
//结果:Stu{sid=1, sname='张三', clazz=Class{cid=1001, cname='高三1班'}}
-
association:一个bean对象关联另一个对象
-
property:bean类中的属性,
-
javaType:用来指定要映射的java类型
-
select:指定下一个执行的sql语句,要用类名+方法名的方式指定
-
column:指定传入下一个sql的执行参数
-
fetchType=“lazy” :开启懒加载(局部设置,只对当前的association关联的sql语句起作用)
-
分步查询
思路是:先查询一个语句,根据查询结果将关联的字段传给下一个sql语句,分步查询
<resultMap id="studentResultMapByStep1" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
column="cid" />
</resultMap>
sqlSept1: select sid,sname,cid from t_stu where sid=#{id}
sqlSept2: select cname,cid from t_class where cid=#{cid}
sqlSept1中查询结果为:sid,sname,cid
sqlSept2的执行条件是要有#{cid}
参数传进来
而在resultMap中指定:
- sid值传给Type为Stu中的属性sid
- sname值传给Type为Stu中的属性sname
- association说明clazz属性是一个对象,类似于bean对象里套bean对象
- select:指定下一个执行的sql语句,要用类名+方法名的方式指定
- column:指定传入下一个sql的执行参数(sqlSept2的执行需要传入cid,所以这里传入的是cid)
注意!
<association property="clazz"
select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
column="cid" />
sqlSept2: select cname,cid from t_class where cid=#{cid}
这里association的column传入的是查询结果中的字段名(有别名用别名),而sqlSept2中#{cid}只是接收参数
案例1
根据id获取学生信息,同时获取学生关联的班级信息
public interface StuMapper {Stu selectBySidStep1(Integer id);}
public interface ClazzMapper { Clazz selectByCidStep2 (Integer cid);}
<resultMap id="studentResultMapByStep1" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
column="cid"
/>
</resultMap>
<select id="selectBySidStep1" resultMap="studentResultMapByStep1">
select
sid,sname,cid
from
t_stu
where
sid=#{id}
</select>
<select id="selectByCidStep2" resultType="Clazz">
select
cname,cid
from
t_class
where
cid=#{cid}
</select>
@Test
public void TestSelectBySidStep1(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
Stu stu = mapper.selectBySidStep1(5);
System.out.println(stu);
}
执行结果:
Preparing: select sid,sname,cid from t_stu where sid=?
Parameters: 5(Integer)
Preparing: select cname,cid from t_class where cid=?
Parameters: 1002(Integer)
从结果不难看出:确实是分步执行的
分步查询的好处:
- 代码复用性高,把一大堆代码拆分成一段一段,每个段都可以单独使用
- 支持懒加载,提高性能
懒加载跟饿汉
懒加载:当需要的时候,才去加载,不需要的时候,就不加载,提高性能
饿汉:不管需不需要,都一直加载,一直有
案例2
案例1使用懒加载
<resultMap id="studentResultMapByStep1" type="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.aw.mybatis.mapper.ClazzMapper.selectByCidStep2"
column="cid"
fetchType="lazy"/>
</resultMap>
@Test
public void TestSelectBySidStep1(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
Stu stu = mapper.selectBySidStep1(5);
System.out.println(stu.getSname());
}
//Preparing: select sid,sname,cid from t_stu where sid=? Parameters: 5(Integer)
//龙七
@Test
public void TestSelectBySidStep1(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
Stu stu = mapper.selectBySidStep1(5);
System.out.println(stu);
// System.out.println(stu.getSname());
}
//Preparing: select sid,sname,cid from t_stu where sid=? Parameters: 5(Integer)
//Preparing: select cname,cid from t_class where cid=? Parameters: 1002(Integer)
//Stu{sid=5, sname='龙七', clazz=Class{cid=1002, cname='高三2班'}}
一对多
一个班级对应多个学生
多种映射方式:常见的包括三种
- collection
- 分步查询(常用,优点是可复用,支持懒加载
Collection
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
select c.cid,c.cname,s.sid,s.sname from t_class c left join t_stu s on c.cid=s.cid where c.cid=#{cid}
sql中查询结果为:sid,sname,cid,cname
而在resultMap中指定:
- cid值传给Type为Clazz中的属性cid
- cname值传给Type为Clazz中的属性cname
- sid值传给Clazz中集合名称为stus,Clazz中集合的类型为Stu
- stus是集合对象,集合对象里有很多个类型为Stu的对象
- Stu对应着t_stu表,Clazz对应着t_class表,而Clazz中有集合Stu说明:
- 一对多的关系,一个班级里面有多个学生,对应到java实体关系上:Clazz里有Stu的集合
案例
根据班级id,查询班级里的学生
package com.aw.mybatis.bean;
import java.util.List;
public class Clazz {
private Integer cid;
private String cname;
private List<Stu> stus;
//所有属性的get,set,toString,构造方法此处忽略不写(节省位置)实际要写
//Stu类里面有cid,cname,clazz属性
}
public interface ClazzMapper {
Clazz selectByCollection(Integer cid);
}
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="Stu">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByCollection" resultMap="clazzResultMap">
select c.cid,c.cname,s.sid,s.sname from t_class c left join t_stu s on c.cid=s.cid where c.cid=#{cid}
</select>
@Test
public void TestSelectByCollection(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByCollection(1001);
System.out.println(clazz);
}
输出结果:
Clazz{
cid=1001,
cname=‘高三1班’,
stus=[Stu{sid=1, sname=‘张三’, clazz=null}, Stu{sid=2, sname=‘李四’, clazz=null}, Stu{sid=3, sname=‘王五’, clazz=null}]
}
分步查询
思路是:先查询一个语句,根据查询结果将关联的字段传给下一个sql语句,分步查询
<resultMap id="resultMapStep1" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection
property="stus"
select="com.aw.mybatis.mapper.StuMapper.selectByCidStep2"
fetchType="lazy"
column="cid"
/>
</resultMap>
Step1: select cid ,cname from t_class where cid = #{cid}
Step2: select sid,sname from t_stu where cid =#{id}
sqlSept1中查询结果为:sid,sname,cid
sqlSept2的执行条件是要有#{cid}
参数传进来
而在resultMap中指定:
-
cid值传给Type为Clazz中的属性cid
-
cname值传给Type为Clazz中的属性cname
-
cid值传给Clazz中集合名称为stus,Clazz中集合的类型为Stu
- stus是集合对象,集合对象里有很多个类型为Stu的对象
- select:指定下一个执行的sql语句,要用类名+方法名的方式指定
- column:指定将什么传入下一个sql
案例
一个班级对应多个学生
public interface StuMapper {
List<Stu> selectByCidStep2(Integer id);}
public interface ClazzMapper {
Clazz selectByCidStep1(Integer cid);}
<resultMap id="resultMapStep1" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection
property="stus"
select="com.aw.mybatis.mapper.StuMapper.selectByCidStep2"
fetchType="lazy"
column="cid"
/>
</resultMap>
<select id="selectByCidStep1" resultMap="resultMapStep1">
select cid ,cname from t_class where cid = #{cid}
</select>
<select id="selectByCidStep2" resultType="Stu">
select sid,sname from t_stu where cid =#{id}
</select>
@Test
public void TestSelectByCidStep1(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByCidStep1(1001);
System.out.println(clazz);
//Clazz{cid=1001, cname='高三1班', stus=[Stu{sid=1, sname='张三', clazz=null}, Stu{sid=2, sname='李四', clazz=null}, Stu{sid=3, sname='王五', clazz=null}]}
}
缓存机制
执行DQL(select)的时候,将查询结果放到缓存(内存)当中,如果下一次还是执行完全相同的DQL
语句,直接从缓存中拿数据,不再查数据库,不去硬盘上找数据了
目的:提高执行效率,使用减少IO的方法来提高效率
mybatis缓存包括:
- 一级缓存:将查询到的数据存储到SqlSesion中
- 二级缓存:将查询到的数据存储到的SqlSessionFactory中
- 或者其他的第三发缓存:EhCache(java开发),Memcache(c开发)
一级缓存
一级缓存是默认开启的,不需要做任何配置
原理:只要使用同一个SqlSession对象执行同一条sql语句,就会走缓存
@Test
public void TestSelectById(){
SqlSession sqlSession =SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = mapper.selectById(2);
System.out.println(car1);
Car car2 = mapper.selectById(2);
System.out.println(car2);
sqlSession.close();
}
2022-11-05 00:14:53.453 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Preparing: select * from t_car where id=?
2022-11-05 00:14:53.473 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:14:53.487 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <== Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
从结果来看只执行了一次sql,但是输出了两次结果,说明走了缓存
下面测试不是同一个sqlSession对象会怎么样
@Test
public void TestSelectById() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = mapper.selectById(2);
System.out.println(car1);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(2);
System.out.println(car2);
sqlSession2.close();
sqlSession.close();
}
2022-11-05 00:17:35.356 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Preparing: select * from t_car where id=?
2022-11-05 00:17:35.375 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:17:35.387 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <== Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
2022-11-05 00:17:35.396 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Preparing: select * from t_car where id=?
2022-11-05 00:17:35.396 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 00:17:35.397 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <== Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
执行了两次sql,说明的确一级缓存就是通过SqlSession实现的
什么时候不走缓存?
-
SqlSession不是同一个,肯定不走缓存
-
查询条件不一样,肯定不走缓存
什么时候一级缓存失效?
第一次DQL跟第二次DQL之间做了以下任意一件事,一级缓存就会失效
- 执行了SqlSession.clearCache(),手动清除缓存
- 执行了INSERT或者UPDATE,不管操作哪张表,都会清除一级缓存
二级缓存
二级缓存的范围是SqlSessionFactory
使用二级缓存需要具备以下几个条件:
- 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,不需要配置,意思就是默认开启
- 在需要使用二级缓存的sqlMapper.xml文件中添加配置:
- 在使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
- SqlSession对象关闭或者提交之后,一级缓存中的数据才会被写到二级缓存中,此时二级缓存才可用
public class Car implements Serializable {}
<mapper namespace="com.aw.mybatis.mapper.CarMapper">
<cache/>
<select id="selectById2" resultType="Car">
select * from t_car where id=#{id}
</select>
</mapper>
@Test
public void TestSelectById2() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(2);//此时car1数据存储到一级缓存当中
System.out.println(car1);
sqlSession1.close();//此时car1数据存储到二级缓存当中
Car car2 = mapper2.selectById(2);//此时二级缓存中有数据,直接取数据
System.out.println(car2);
}
2022-11-05 10:18:08.861 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Preparing: select * from t_car where id=?
2022-11-05 10:18:08.880 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - ==> Parameters: 2(Integer)
2022-11-05 10:18:08.894 [main] DEBUG com.aw.mybatis.mapper.CarMapper.selectById - <== Total: 1
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
2022-11-05 10:18:08.915 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23986957]
2022-11-05 10:18:08.916 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@23986957]
2022-11-05 10:18:08.917 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 597190999 to pool.
2022-11-05 10:18:08.921 [main] DEBUG com.aw.mybatis.mapper.CarMapper - Cache Hit Ratio [com.aw.mybatis.mapper.CarMapper]: 0.5
Car{id=2, carNum='47', brand='迈凯伦', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'}
什么时候二级缓存失效?
只要两次查询之间出现了增删改操作,二级缓存就会失效,一级缓存也会失效
二级缓存的配置信息
-
eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
-
- LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
- FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
- SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
- WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
-
flushInterval:
-
- 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
-
readOnly:
-
- true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
- false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
-
size:
-
- 设置二级缓存中最多可存储的java对象数量。默认值1024。
MyBatis集成EhCache
集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语言写的。所以mybatis集成EhCache较为常见,按照以下步骤操作,就可以完成集成:
第一步:引入mybatis整合ehcache的依赖。
<!--mybatis集成ehcache的组件-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
第二步:在类的根路径下新建echcache.xml文件,并提供以下配置信息。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
<diskStore path="e:/ehcache"/>
<!--defaultCache:默认的管理策略-->
<!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
<!--maxElementsInMemory:在内存中缓存的element的最大数目-->
<!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
<!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
<!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
<!--FIFO:first in first out (先进先出)-->
<!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
<!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
</ehcache>
第三步:修改SqlMapper.xml文件中的<cache/>标签,添加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);
}
逆向工程
所谓的逆向工程是:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
要完成这个工作,需要借助别人写好的逆向工程插件。
思考:使用这个插件的话,需要给这个插件配置哪些信息?
- pojo类名、包名以及生成位置。
- SqlMapper.xml文件名以及生成位置。
- Mapper接口名以及生成位置。
- 连接数据库的信息。
- 指定哪些表参与逆向工程。
- …
实现
步骤1
在pom.xml文件中添加逆向工程插件:
<!--定制构建过程-->
<build>
<!--可配置多个插件-->
<plugins>
<!--其中的一个插件:mybatis逆向工程插件-->
<plugin>
<!--插件的GAV坐标-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<!--允许覆盖-->
<configuration>
<overwrite>true</overwrite>
</configuration>
<!--插件的依赖-->
<dependencies>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
步骤2:
配置generatorConfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime有两个值:
MyBatis3Simple:生成的是基础版,只有基本的增删改查。
MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
-->
<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>
<!-- 生成pojo包名和位置 -->
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
<!--是否开启子包-->
<property name="enableSubPackages" value="true"/>
<!--是否去除字段名的前后空白-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成SQL映射文件的包名和位置 -->
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
<!--是否开启子包-->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 生成Mapper接口的包名和位置 -->
<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>
步骤3:
运行插件
测试测序
@Test
public void TestSelect(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
//查询一个
// Car car = mapper.selectByPrimaryKey(41L);
// System.out.println(car);
//查询所有,根据条件查询,如果是null,就是没有条件,就查询所有
// List<Car> cars = mapper.selectByExample(null);
// cars.forEach(car -> System.out.println(car));
//条件查询
//封装条件,通过CarExample对象来封装查询条件
CarExample carExample = new CarExample();
//根据createCriteria创造查询条件
//andBrandEqualTo brand等于(“小鹏”)
//andCarTypeEqualTo 和carType等于(“燃油”)
carExample.createCriteria().andBrandEqualTo("小鹏").andCarTypeEqualTo("燃油");
//或者
//andBrandEqualTo brand等于(“小鹏”)
//andCarTypeEqualTo 和carType等于(“电气”)
carExample.or().andBrandEqualTo("小鹏").andCarTypeEqualTo("电气");
List<Car> cars1 = mapper.selectByExample(carExample);
cars1.forEach(car -> System.out.println(car));
sqlSession.close();
}
运行结果:
select id, car_num, brand, guide_price, produce_time, car_type from t_car WHERE ( brand = ? and car_type = ? ) or( brand = ? and car_type = ? )
Mybatis分页
pageNum:页码
pageSize:每页显示的记录条数
limit计算方法:limit (pageNum-1)*pageSize , pageSize
(pageNum-1)*pageSize:每页的起始下标
PageHelper插件
使用PageHelper插件进行分页,更加的便捷。
步骤一
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
步骤二
在mybatis-config.xml文件中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
步骤三
编写java程序
public interface CarMapper {
List<Car> selectAll();
}
<select id="selectAll" resultMap="resultMapByPage">
select * from t_car
</select>
关键点:
- 在查询语句之前开启分页功能。
- 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)
public class CarMapperTest {
@Test
public void TestSelectByPage(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
PageHelper.startPage(2, 2);
List<Car> cars = mapper.selectAll();
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
System.out.println(pageInfo);
}
}
结果:
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
[Car{id=29, carNum='46', brand='雷克萨斯', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'},
Car{id=30, carNum='47', brand='雷克萨斯', guidePrice=3.0, produceTime='2022-8-5', carTyppe='新能源'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}
注解开发
mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。
当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。
官方是这么说的:
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
原则:简单sql可以注解。复杂sql使用xml。
Insert
public interface CarMapper {
@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
}
@Test
public void TestInsert(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null,"55","奔驰",2.0,"2009-4-1","电气");
int count = mapper.insert(car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
Num:页码
pageSize:每页显示的记录条数
limit计算方法:limit (pageNum-1)*pageSize , pageSize
(pageNum-1)*pageSize:每页的起始下标
PageHelper插件
使用PageHelper插件进行分页,更加的便捷。
步骤一
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
步骤二
在mybatis-config.xml文件中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
步骤三
编写java程序
public interface CarMapper {
List<Car> selectAll();
}
<select id="selectAll" resultMap="resultMapByPage">
select * from t_car
</select>
关键点:
- 在查询语句之前开启分页功能。
- 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)
public class CarMapperTest {
@Test
public void TestSelectByPage(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
PageHelper.startPage(2, 2);
List<Car> cars = mapper.selectAll();
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
System.out.println(pageInfo);
}
}
结果:
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=15, pages=8,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=15, pages=8, reasonable=false, pageSizeZero=false}
[Car{id=29, carNum='46', brand='雷克萨斯', guidePrice=4.0, produceTime='2022-8-5', carTyppe='新能源'},
Car{id=30, carNum='47', brand='雷克萨斯', guidePrice=3.0, produceTime='2022-8-5', carTyppe='新能源'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}
注解开发
mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。
当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。
官方是这么说的:
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
原则:简单sql可以注解。复杂sql使用xml。
Insert
public interface CarMapper {
@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
}
@Test
public void TestInsert(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null,"55","奔驰",2.0,"2009-4-1","电气");
int count = mapper.insert(car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
Select
public interface CarMapper {
@Results({
@Result(property = "id",column = "id"),
@Result(property = "carNum",column = "car_num"),
@Result(property = "brand",column = "brand"),
@Result(property = "guidePrice",column = "guide_price"),
@Result(property = "produceTime",column = "produce_time"),
@Result(property = "carType",column = "car_type"),
})
@Select("select * from t_car where id=#{id}")
Car select(Integer id);
}
@Test
public void TestSelect(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.select(29);
System.out.println(car);
}
Update
public interface CarMapper {
@Update("update t_car set brand=#{brand} where id=#{id}")
int update(@Param("brand") String brand,@Param("id") Integer id);
}
@Test
public void TestUpdate(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.update("马德里", 30);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
Delete
public interface CarMapper {
@Delete("delete from t_car where id=#{id}")
int delete(Integer id );
}
@Test
public void TestDelete(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.delete(28);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}