1. MyBatis
ORM:对象关系映射
O(Object):Java虚拟机中的Java对象
R(Relational):关系型数据库
M(Mapping):将Java虚拟机中的Java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象。
1.1 MyBatis入门程序
CarMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace先随意写一个-->
<mapper namespace="car">
<!--insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,'102','丰田mirai',40.30,'2014-10-05','氢能源')
</insert>
</mapper>
测试程序
public class MyBatisIntroductionTest {
public static void main(String[] args) {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 创建SqlSessionFactory对象
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3. 创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 执行sql
int count = sqlSession.insert("insertCar"); // 这个"insertCar"必须是sql的id
System.out.println("插入几条数据:" + count);
// 5. 提交(mybatis默认采用的事务管理器是JDBC,默认是不提交的,需要手动提交。)
sqlSession.commit();
// 6. 关闭资源(只关闭是不会提交的)
sqlSession.close();
}
}
1.2 工具类SqlSessionUtil
public class SqlSessionUtil {
// 工具类的构造方法一般都是私有化的
// 工具类中所有的方法都是静态的,直接采用类名即可调用,不需要new对象
// 为了防止new对象,构造方法私有化
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
// 类加载时执行
// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取会话对象
* @return
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
}
2. MyBatis完成CRUD
<insert id="insertCarByPOJO">
<!--#{} 里写的是POJO的属性名-->
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
2.1 Map传参
public class CarMapperTest {
@Test
public void testInsertCar(){
// 准备数据
Map<String, Object> map = new HashMap<>();
// 让key的可读性增强
map.put("carNum", "103");
map.put("brand", "奔驰E300L");
map.put("guidePrice", 50.3);
map.put("produceTime", "2020-10-01");
map.put("carType", "燃油车");
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句(使用map集合给sql语句传递数据)
int count = sqlSession.insert("insertCar", map);
System.out.println("插入了几条记录:" + count);
}
}
2.2 POJO传参
@Test
public void testInsertCarByPOJO(){
// 创建POJO,封装数据
Car car = new Car();
car.setCarNum("103");
car.setBrand("奔驰C200");
car.setGuidePrice(33.23);
car.setProduceTime("2020-10-11");
car.setCarType("燃油车");
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL,传数据
int count = sqlSession.insert("insertCarByPOJO", car);
System.out.println("插入了几条记录" + count);
}
如果采用map集合传参,#{} 里写的是map集合的key,如果key不存在不会报错,数据库表中会插入NULL。
如果采用POJO传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写(例如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get方法不存在会报错。
2.3 update
<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>
2.4 Select
resultType 告诉mybatis返回一个什么类型的Java对象
<select id="selectCarById" resultType="com.powernode.mybatis.pojo.Car">
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
where
id = #{id}
</select>
<select id="selectCarAll" resultType="com.powernode.mybatis.pojo.Car">
<!--记得使用as起别名,让查询结果的字段名和java类的属性名对应上。-->
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
</select>
2.4.1 查询一条语句
@Test
public void testSelectCarById(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
Object car = sqlSession.selectOne("selectCarById", 1);
System.out.println(car);
}
2.4.2 查询多条语句
@Test
public void testSelectCarAll(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
4. MyBatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
<mapper resource="CarMapper2.xml"/>
</mappers>
</configuration>
1. configuration:根标签,表示配置信息。
2. environments:环境(多个),以“s”结尾表示复数,也就是说mybatis的环境可以配置多个数据源。
default属性:表示默认使用的是哪个环境,default后面填写的是environment的id。default的值只需要和environment的id值一致即可。
3. environment:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置)
id:给当前环境一个唯一标识,该标识用在environments的default后面,用来指定默认环境的选择。
4. transactionManager:配置事务管理器
type属性:指定事务管理器具体使用什么方式,可选值包括两个
JDBC:使用JDBC原生的事务管理机制。底层原理:事务开启conn.setAutoCommit(false); ...处理业务...事务提交conn.commit();
MANAGED:交给其它容器来管理事务,比如WebLogic、JBOSS等。如果没有管理事务的容器,则没有事务。没有事务的含义:只要执行一条DML语句,则提交一次。
5. dataSource:指定数据源
type属性:用来指定具体使用的数据库连接池的策略,可选值包括三个
5.1 UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
driver 这是 JDBC 驱动的 Java 类全限定名。url 这是数据库的 JDBC URL 地址。
username 登录数据库的用户名。password 登录数据库的密码。
defaultTransactionIsolationLevel 默认的连接事务隔离级别。
defaultNetworkTimeout 等待数据库操作完成的默认网络超时时间(单位:毫秒)
5.2 POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。
property可以是(除了包含UNPOOLED中之外):
poolMaximumActiveConnections 在任意时间可存在的活动(正在使用)连接数量,默认值:10
poolMaximumIdleConnections 任意时间可能存在的空闲连接数。
5.3 JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。如果不是web或者maven的war工程,JNDI是不能使用的。
initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
data_source 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
6. mappers:在mappers标签中可以配置多个sql映射文件的路径。
7. mapper:配置某个sql映射文件的路径
resource属性:使用相对于类路径的资源引用方式;
url属性:使用完全限定资源定位符(URL)方式;
4.1 environment
<!--默认使用开发环境-->
<!--<environments default="dev">-->
<!--默认使用生产环境-->
<environments default="production">
// 一个数据库对应一个SqlSessionFactory对象
// 两个数据库对应两个SqlSessionFactory对象,以此类推
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 使用默认数据库
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession(true);
int count = sqlSession.insert("insertCar", car);System.out.println("插入了几条记录:" + count);
// 使用指定数据库
SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "dev");
SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
int count1 = sqlSession1.insert("insertCar", car);
System.out.println("插入了几条记录:" + count1);
4.2 transactionManager
<!--
transactionManager标签:
1.作用:配置事务管理器。指定mybatis具体使用什么方式去管理事务
2.type属性有两个值:
第一个:JDBC:使用原生的JDBC代码来管理事务。
conn.setAutoCommit(false);
....
conn.commit();
第二个:MANAGED:mybatis不再负责事务的管理,将事务管理交给其它的JEE(JavaEE)容器来管理。例如:spring
3. 不区分大小写,但是只能使用这两个值
4. 在mybatis中提供了一个事务管理器接口:Transaction
该接口下有两个实现类:
JdbcTransaction
ManagedTransaction
如果type="JDBC",那么底层会实例化JdbcTransaction对象
如果type="MANAGED",那么底层会实例化ManagedTransaction
-->
<transactionManager type="JDBC"/>
事务管理器是:JDBC
采用JDBC的原生事务机制:
开启事务:conn.setAutoCommit(false);
处理业务......
提交事务:conn.commit();
事务管理器是:MANAGED
交给容器去管理事务,但目前使用的是本地程序,没有容器的支持,当mybatis找不到容器的支持时:没有事务。也就是说只要执行一条DML语句,则提交一次。
4.3 dataSource
<!--
dataSource配置:
1.dataSource被称为数据源
2.dataSource作用是什么?为程序提供Connection对象。(但凡是给程序提供Connection对象的,都叫做数据源。)
3.数据源实际上是一套规范。JDK中有这套规范:javax.sql.DataSource(这个数据源的规范,这套接口实际上是JDK规定的。)
4.我们自己也可以编写数据源组件,只要实现javax.sql.Datasource接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。
比如你可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源)。
5.常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?
阿里巴巴的德鲁伊连接池:druid
c3p0
dbcp
..
6.type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:
type属性有三个值:必须是三选一。
type="[UNPOOLED|POOLED|JNDI]"
UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。
PO0LED:使用mybatis自已实现的数据库连接池。
JNDI:集成其它第三方的数据库连接池。(规范)
如果想使用dbcp、c3p0、druid(德鲁伊)等,需要使用这种方式。
大部分的web容器(Tomcat、Jetty、WebLogic、WebSphere)也都实现了 JNDI规范
JNDI是:java命名目录接口
-->
<dataSource type="POOLED">
4.4 properties
<dataSource type="POOLED">
<!--采用的是property标签的name属性-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernodemb"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
<!--提醒:正常使用连接池的话,池中有很多参数是需要设置的。设置好参数,可以让连接池发挥的更好。事半功倍的效果。-->
<!--具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试。-->
<!--poolMaximumActiveconnections:连接池当中最多的正在使用的连接对象的数量上限。最多有多少个连接可以活动。默认值10-->
<property name="poolMaximumActiveConnections" value="3"/>
<!--每隔2秒打印日志,并且尝试获取连接对象-->
<property name="poolTimeToWait" value="2000"/>
<!--强行让某个连接空闲,超时时间的设置-->
<property name="poolMaximumCheckoutTime" value="10000"/>
<!--最多的空闲数量-->
<property name="poolMaximumIdleConnections" value="5"/>
</dataSource>
4.5 mapper
<mappers>
<!--sql映射文件(Mapper文件)创建好之后,需要将该文件路径配置到这里-->
<!--resource属性自动会从类的根路径下开始查找资源-->
<mapper resource="CarMapper.xml"/>
</mappers>
5. 在Web中应用MyBatis
5.1 MyBatis对象作用域
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。修改SqlSessionUtil工具类:
5.2 SqlSessionUtil工具类
public class SqlSessionUtil {
// 工具类的构造方法一般都是私有化的
// 工具类中所有的方法都是静态的,直接采用类名即可调用,不需要new对象
// 为了防止new对象,构造方法私有化
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
// 类加载时执行
// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 全局的,服务器级别的,一个服务器当中定义一个即可
// 将SqlSession对象放到ThreadLocal当中,为了保证一个线程对应一个SqlSession
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 获取会话对象,该会话支持自动提交。
* @return
*/
public static SqlSession openSession() {
// return sqlSessionFactory.openSession();
SqlSession sqlSession = local.get();
if(sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
// 将sqlSession对象绑定到当前线程上
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭SqlSession对象(从当前线程中移除SqlSession对象)
* @param sqlSession
*/
public static void close(SqlSession sqlSession) {
if(sqlSession != null) {
sqlSession.close();
// 移除SqlSession对象和当前线程的绑定关系
// 因为tomcat服务器支持线程池,用过的线程对象t1,下一次还会使用
local.remove();
}
}
}
5.3 javassist生成实现类
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);
}
}
}
6. MyBatis接口代理机制
@Test
public void testInsert(){
SqlSession sqlSession = SqlSessionUtil.openSession();
// 面向接口获取接口的代理对象
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "8654", "小米yu7", 35.09, "2020-10-10", "新能源");
int count = mapper.insert(car);
System.out.println(count);
sqlSession.commit();
}
AccountMapper.xml文件中的namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致。
7. MyBatis的小技巧
#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。 字符串,在sql语句中应该添加单引号,所以不适合使用${}。
<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}-->
<!--car_type = ${carType}-->
car_type = '${carType}'
</select>
</mapper>
7.1 必须使用${}
当需要进行sql语句关键字拼接的时候。必须使用${}
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。
<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}-->
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语句当中应该使用#{} 还是 ${} 呢?
使用#{}会是这样:select * from 't_car'
使用${}会是这样:select * from t_car
/**
* 根据id批量删除
* @param ids
* @return
*/
int deleteBatch(String ids);
<delete id="deleteBatch">
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();
}
模糊查询
<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>
/**
* 根据品牌进行模糊查询
* @param likeBrank
* @return
*/
List<Car> selectLikeByBrand(String likeBrank);
@Test
public void testSelectLikeByBrand(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectLikeByBrand("奔驰");
cars.forEach(car -> System.out.println(car));
}
第一种方案:
'%${brand}%'
第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接
concat('%',#{brand},'%')
第三种方案:比较鸡肋了。可以不算。
concat('%','${brand}','%')
第四种方案:
"%"#{brand}"%"
7.2 typeAliases 起别名
<properties resource="jdbc.properties"/>
<!--起别名的标签-->
<typeAliases>
<typeAlias type="com.powernode.pojo.Car" alias="aaa"/>
<typeAlias type="com.powernode.pojo.Log" alias="bbb"/>
<!--采用默认的别名机制, 别名就是类的简名:com.powernode.pojo.Car指的就是Car-->
<!--alias是大小写不敏感的。也就是说假设alias="Car",再用的时候,可以CAR,也可以car,也可以Car-->
<typeAlias type="com.powernode.pojo.Car"/>
<typeAlias type="com.powernode.pojo.Log"/>
<!--包下所有的类自动起别名。使用简名作为别名。-->
<package name="com.powernode.pojo"/>
</typeAliases>
7.3 mappers
SQL映射文件的配置方式包括四种:
resource:从类路径中加载
url:从指定的全限定资源路径中加载
class:使用映射器接口实现类的完全限定类名
package:将包内的映射器接口实现全部注册为映射器
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<mapper resource="CarMapper.xml"/> 要求类的根路径下必须有:CarMapper.xml
<mapper url="file:///d:/CarMapper.xml"/> 要求在d:/下有CarMapper.xml文件
<mapper class="全限定接口名,带有包名"/>
<mappers>
<!-- <mapper resource="CarMapper.xml"/>-->
<!-- <mapper resource="LogMapper.xml"/>-->
<package name="com.hnlg.mapper"/>
</mappers>
mapper标签的属性可以有三个:
resource:这种方式是从类的根路径下开始查找资源。采用这种方式的话,配置文件需要放到类路径当中才行。
url: 绝对路径的方式,这种方式不要求配置文件必须放到类路径当中,哪里都行,只要提供一个绝对路径就行。这种方式使用极少,因为移植性太差。
class: 这个位置提供的是mapper接口的全限定接口名,必须带有包名的。
思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?
<mapper class="com.powernode.mapper.CarMapper"/>
如果你class指定是:com.powernode.mapper.CarMapper
那么mybatis框架会自动去com/powernode/mapper目录下查找CarMapper.xml文件。
注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。
CarMapper接口-> CarMapper.xml
LogMapper接口-> LogMapper.xml
....
<package name="com.hnlg.mapper"/> 最常用的
提醒!!!!!!!!!!!!!!!!!!!!!!!
在IDEA的resources目录下新建多重目录的话,必须是这样创建:
com/powernode/mybatis/mapper
不能这样:
com.powernode.mybatis.mapper
7.4 插入数据获取自动生成主键
<!--
1. useGeneratedKeys="true"使用自动生成的主键值
2. keyProperty="id"指定主键值赋值给对象的哪个属性。
-->
<insert id="insertCarUseGneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
</insert>
8. MyBatis参数处理
8.1 单个简单类型参数
/**
* 根据birth查询
* @param birth
* @return
*/
List<Student> selectByBirth(Date birth);
/**
* 根据sex查询
* @param sex
* @return
*/
List<Student> selectBySex(Character sex);
<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>
@Test
public void testSelectByBirth() throws ParseException {
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date birth = sdf.parse("2005-09-06");
List<Student> students = mapper.selectByBirth(birth);
students.forEach(System.out::println);
sqlSession.close();
}
@Test
public void testSelectBySex(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
/*char类型对应的包装类*/
Character sex = Character.valueOf('男');
List<Student> students = mapper.selectBySex(sex);
students.forEach(System.out::println);
sqlSession.close();
}
8.2 Map类型参数
/**
* 根据name和age查询
* 手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。
* @param paramMap
* @return
*/
List<Student> selectByParamMap(Map<String,Object> paramMap);
<select id="selectByParamMap" resultType="Student">
select * from t_student where name = #{nameKey} and age = #{ageKey}
</select>
@Test
public void testSelectByParamMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 准备Map
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("nameKey", "张三");
paramMap.put("ageKey", 20);
List<Student> students = mapper.selectByParamMap(paramMap);
students.forEach(System.out::println);
sqlSession.close();
}
8.3 多参数
/**
* 根据name和sex查询Student信息、
* 如果是多个参数的话,mybatis框架底层是怎么做的呢?
* mybatis框架会自动创建一个ap集合。并且Map集合是以这种方式存储参数的:
* map.put("arg0",name);
* map.put("arg1",sex);
* map.put("param1", name);
* map.put("param2", sex);
* @param name
* @param sex
* @return
*/
List<Student> selectByNameAndSex(String name, Character sex);
<select id="selectByNameAndSex" resultType="Student">
/*select * from t_student where name = #{name} and sex = #{sex} 代码错误*/
/*select * from t_student where name = #{arg0} and sex = #{arg1}*/
select * from t_student where name = #{arg0} and sex = #{param1}
</select>
@Test
public void testSelectByNameAndSex(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectByNameAndSex("张三", '男');
students.forEach(System.out::println);
sqlSession.close();
}
8.3.1 @Param注解
/**
* Param注解底层
* map.put("name",name);
* map.put("sex",sex);
*
* @param name
* @param sex
* @return
*/
List<Student> selectByNameAndSex2(@Param("name") String name, @Param("sex") Character sex);
<!--arg0和arg1失效了,但是param0和param1还有效;但是主要还是使用@Param注解中的内容-->
<select id="selectByNameAndSex2" resultType="student">
select * from t_student where name = #{name} and sex = #{sex}
</select>
9. MyBatis查询语句专题
9.1 返回Map
/**
* 根据id查询返回map集合对象
* @param id
* @return
*/
Map<String, Object> selectByIdReturnMap(Long id);
@Test
public void testSelectByIdReturnMap() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper carMapper = sqlSession.getMapper(CarMapper.class);
Map<String, Object> car = carMapper.selectByIdReturnMap(1L);
System.out.println(car);
sqlSession.close();
}
<select id="selectByIdReturnMap" resultType="Map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>
9.2 返回list<Map>
/**
* 查询所有的Car,返回一个List集合。List集合中存储的是Map集合。
* @return
*/
List<Map<String,Object>> selectAllRetListMap();
@Test
public void testSelectAllRetListMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Map<String,Object>> cars = mapper.selectAllRetListMap();
cars.forEach(System.out::println);
sqlSession.close();
}
<select id="selectAllRetListMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
9.3 返回Map<String, Map>
/**
* 获取所有的Car,返回一个Map集合。
* Map集合的key是Car的id。
* Map集合的value是对应Car。
* @return
*/
@MapKey("id") // 将查询结果的id值作为整个大Map集合的key
Map<Long,Map<String,Object>> selectAllRetMap();
@Test
public void testSelectAllRetMap() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Map<Long, Map<String,Object>> cars = mapper.selectAllRetMap();
System.out.println(cars);
sqlSession.close();
}
<select id="selectAllRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
9.4 resultMap结果映射
查询结果的列名和java对象的属性名对应不上怎么办?
第一种方式:as 给列起别名
第二种方式:使用resultMap进行结果映射
第三种方式:是否开启驼峰命名自动映射(配置settings)
<!--
1. 不再起别名了,起一个结果映射,在这个结果映射当中指定数据库表的字段名和java类的属性名的对应关系
2. type属性:用来指定POJO类的类名
3. id属性:指定resultMap的唯一标识,这个id要在select标签中使用
type="com.hnlg.pojo.Car" 因为
<typeAliases>
<package name="com.hnlg.pojo"/>
</typeAliases> 所以为Car
-->
<resultMap id="CarResultMap" type="Car">
<!--如果数据库表中有主键,建议配置一个id标签-->
<id property="id" column="id"/>
<!--property填写POJO类的属性名-->
<!--column填写数据库表的字段名-->
<result property="carNum" column="car_num"/>
<!--当属性名和数据库列名一致时,可以省略。但建议都写上。-->
<!--javaType用来指定属性类型。jdbcType用来指定列类型。一般可以省略。-->
<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标签的resultMap属性,用来指定使用哪个结果映射。resultMap后面的值是resultMap的id-->
<select id="selectAllByResultMap" resultMap="CarResultMap">
select * from t_car
</select>
9.5 驼峰命名自动映射
使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
SQL命名规范:全部小写,单词之间采用下划线分割。
<!--放在properties标签后面-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--开启自动驼峰映射-->
<!--不开启的话很多值会出现null值-->
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select * from t_car
</select>
/**
* 查询所有Car,启用驼峰命名自动映射
* @return
*/
List<Car> selectAllByMapUnderscoreToCamelCase();
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByMapUnderscoreToCamelCase();
cars.forEach(System.out::println);
sqlSession.close();
}
9.6 返回总记录条数
<select id="selectTotal" resultType="long">
/* 可以是count(*)也可以是count(1),但是如果count(某个字段)会自动去除null值 */
select count(*) from t_car
</select>
10. 动态SQL
10.1 if标签
/**
* 多条件查询Car
* 多参数查询时需要使用到 @Param 注解
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByMultiCondition" resultType="Car">
select * from t_car where 0 = 0
<!--
1.if标签中test属性是必须的。
2.if标签中test属性的值是false或者true。
3.如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。
4.test属性中可以使用的是:
当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param("brand"),那么这里只能使用brand
当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2....
当使用了P0J0,那么test中出现的是P0J0类的属性名。
5.在mybatis的动态SQL当中,不能使用&&,只能使用and。
-->
<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>
10.2 where标签
where标签的作用:让where子句更加动态智能。
所有条件都为空时,where标签保证不会生成where子句。
自动去除某些条件前面多余的and或or。
如果最后一个条件为空,那么查询语句最后会多出一个 and
<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>
10.3 trim标签
所有条件都为空时,不会生成where子句。
如果最后一个条件为空,查询语句最后不会会多出一个 and
<select id="selectByMultiConditionWithTrim" resultType="car">
select * from t_car
<!--
- prefix:在trim标签中的语句前 添加 内容
- suffix:在trim标签中的语句后 添加 内容
- prefixOverrides:前缀 覆盖掉(去掉)
- suffixOverrides:后缀 覆盖掉(去掉)
-->
<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>
10.4 set标签
使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
/**
* 更新Car Set标签
* @param car
* @return
*/
int updateWithSet(Car car);
@Test
public void testUpdateWithSet(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(10L,"1001","小米su7",10.0,"",null);
mapper.updateWithSet(car);
sqlSession.commit();
sqlSession.close();
}
<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>
10.5 choose when otherwise
需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生产日期查询。
<!--需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生产日期查询。-->
<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>
/**
* 使用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);
@Test
public void testSelectWithChoose(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
//List<Car> cars = mapper.selectWithChoose("丰田霸道", 20.0, "2000-10-10");
//List<Car> cars = mapper.selectWithChoose("", 20.0, "2000-10-10");
//List<Car> cars = mapper.selectWithChoose("", null, "2000-10-10");
// 全为空,则会按照最后一个查,但是全部查询出
List<Car> cars = mapper.selectWithChoose("", null, "");
cars.forEach(System.out::println);
sqlSession.close();
}
10.6 foreach标签
批量添加
<insert id="insertBatch">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null, #{car.carNum}, #{car.brand}, #{car.guidePrice}, #{car.produceTime}, #{car.carType})
</foreach>
</insert>
/**
* 批量插入
* @param cars
* @return
*/
int insertBatch(@Param("cars") List<Car> cars);
@Test
public void testInsertBatch(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = new Car(null, "1002", "尊界S800", 50.98, "2025-09-01", "智能新能源");
Car car2 = new Car(null, "1003", "尊界S801", 50.98, "2025-09-01", "智能新能源");
Car car3 = new Car(null, "1004", "尊界S802", 50.98, "2025-09-01", "智能新能源");
List<Car> cars = new ArrayList<>();
cars.add(car1);
cars.add(car2);
cars.add(car3);
mapper.insertBatch(cars);
sqlSession.commit();
sqlSession.close();
}
批量删除
<!--
foreach标签的属性:
collection:指定数组或者集合
item:代表数组或集合中的元素
separator:循环之间的分隔符
open:foreach循环拼接的所有sql语句的最前面以什么开始。
close:foreach循环拼接的所有sql语句的最后面以什么结束。
collection="ids”第一次写这个的时候报错了,错误信息是:[array,arg0]
什么意思?
map.put("array",数组);
map.put("arg”,数组);
-->
<delete id="deleteByIds">
delete from t_car where id in(
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)
/*delete from t_car where id in
<!--<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>-->
*/
</delete>
<!--根据or进行批量删除-->
<delete id="deleteByIds2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id=#{id}
</foreach>
</delete>
/**
* 根据in进行批量删除, foreach标签
* @param ids
* @return
*/
int deleteByIds(@Param("ids")Long[] ids);
@Test
public void testDeleteByIds(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Long[] ids = {5L, 6L, 7L};
int count = mapper.deleteByIds(ids);
sqlSession.commit();
sqlSession.close();
}
10.7 sql标签与include标签
sql标签用来声明sql片段
include标签用来将声明的sql片段包含到某个sql语句当中
<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType</sql>
<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>
11. MyBatis高级映射及延迟加载
11.1 多对一映射
11.1.1 级联属性映射
public class Student {
private Integer sid;
private String sname;
/*多对一,主对象中添加副对象*/
private Clazz clazz;
}
public class Clazz {
private Integer cid;
private String cname;
}
<!--多对一映射的第一种方式,级联属性映射-->
<!--type="Student"指的是数据库表与对应的java类的映射-->
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select
s.sid, s.sname, c.cid, c.cname
from
t_stu s left join t_clazz c on s.cid = c.cid
where
sid = #{sid}
</select>
@Test
public void testSelectById() {
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectById(1);
System.out.println(student.getSid());
System.out.println(student.getSname());
System.out.println(student.getClazz().getCid());
System.out.println(student.getClazz().getCname());
sqlSession.close();
}
11.1.2 association
其他位置都不需要修改,只需要修改resultMap中的配置:association
<!--多对一映射的第二种方式,association-->
<resultMap id="studentResultMapAssociation" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<!--
一个Student对象关联一个Clazz对象
Association:关联,一个Student对象关联一个Clazz对象
property:指定要映射的POJO类的属性名
javaType:用来指定要映射的java类型
-->
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
</association>
</resultMap>
11.1.3 分步查询(延迟加载)
<!--
分步查询的优点:
第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用)
第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。
什么是延迟加载(懒加载),有什么用?
延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询,
作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
在mybatis当中怎么开启延迟加载呢?
association标签中添加fetchType="lazy"
注意:默认情况下是没有开启延迟加载的。需要设置:fetchType="lazy"
这种在association标签中配置fetchType="lazy",是局部的设置,只对当前的association关联的sqL语句起作用
在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true
实际开发中的模式:把全局的延迟加载打开。
如果某一步不需要使用延迟加载,请设置: fetchType="eager"
-->
<!--
1. 完成多对一的分步查询
2. 根据学生的sid查询学生的所有信息,这些信息当中含有班级id(cid)
-->
<resultMap id="studentResultMapByStep" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.hnlg.mapper.ClazzMapper.selectByIdStep2"
column="cid"
fetchType="eager"/>
<!--association标签中的select 指定第二步SQL语句的id-->
</resultMap>
<select id="selectByIdStep1" resultMap="studentResultMapByStep">
select sid, sname, cid from t_stu where sid = #{sid}
</select>
<!--ClazzMapper.xml文件-->
<!--分步查询第二步:根据cid获取班级信息-->
<select id="selectByIdStep2" resultType="Clazz">
select cid,cname from t_clazz where cid = #{cid}
</select>
@Test
public void testSelectByIdStep1() {
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdStep1(5);
// System.out.println(student);
// 懒加载, 只查看学生的名字
System.out.println(student.getSname());
// 程序执行到这里,要看班级的名字 访问才执行这个SQL语句;
System.out.println(student.getClazz().getCname());
sqlSession.close();
}
全局延迟加载(setting)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
如果要针对某个特定的sql关闭延迟加载机制,那么就在association标签中将fetchType改为eager;
11.2 一对多映射
一对多的实现,通常是在一的一方中有List集合属性。在Clazz类中添加List<Student> stus; 属性。
public class Student {
private Integer sid;
private String sname;
/*多对一,主对象中添加副对象*/
private Clazz clazz;
}
public class Clazz {
private Integer cid;
private String cname;
private List<Student> stus;
}
11.2.1 collection
/**
* 通过collection进行一对多映射
* 根据cid获取Clazz信息
* @param cid
* @return
*/
Clazz selectByCollection(Integer cid);
<!--一对多映射,通过collection-->
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--一对多,collection是集合的意思-->
<!--ofType属性用来指定集合当中的元素类型-->
<collection property="stus" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByCollection" resultMap="clazzResultMap">
select c.cid, c.cname, s.sid, s.sname from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
</select>
11.2.2 分步查询
/**
* 根据班级编号查询班级信息。同时班级中所有的学生信息也要查询。
* 分步查询,第一步,根据班级编号获取班级信息
* @param cid
* @return
*/
Clazz selectByCidStep1(Integer cid);
<!--一对多查询-->
<!--分步查询第一步:根据班级的cid获取班级信息-->
<resultMap id="clazzResultMapStep" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus"
select="com.hnlg.mapper.StudentMapper.selectByCidStep2"
column="cid"/>
</resultMap>
<select id="selectByCidStep1" resultMap="clazzResultMapStep">
select cid,cname from t_clazz where cid = #{cid}
</select>
<!--StudentMapper.xml文件-->
<!--一对多-->
<select id="selectByCidStep2" resultType="Student">
select * from t_stu where cid = #{cid}
</select>
@Test
public void testSelectByCidStep1() {
SqlSession sqlSession = SqlSessionUtil.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByCidStep1(1000);
List<Student> students = clazz.getStus();
System.out.println(clazz);
System.out.println(students);
sqlSession.close();
}
12. MyBatis缓存
缓存的作用:通过减少IO的方式,来提高程序的执行效率。mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。mybatis缓存包括:
一级缓存:将查询到的数据存储到SqlSession中。
二级缓存:将查询到的数据存储到SqlSessionFactory中。
或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。
12.1 xml文件
<mapper namespace="com.hnlg.mapper.CarMapper">
<!--
默认情况下,二级缓存机制是开启的;
只需要在对应的xxxMapper.xml文件中添加以下标签就表示使用二级缓存
-->
<cache/>
<select id="selectById2" resultType="Car">
select * from t_car where id = #{id}
</select>
<select id="selectById" resultType="Car">
select * from t_car where id = #{id}
</select>
</mapper>
12.2 一级缓存
// 思考:什么时候不走缓存?
// SqlSession对象不是同一个,肯定不走缓存
// 查询条件不一样,肯定也不走缓存。
// 思考:什么时候一级缓存失效?
// 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:
// 1.执行了sqlSession的clearCache()方法,这是手动清空缓存。
// 2.执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。
@Test
public void testSelectById() throws IOException {
/*
如果要获取不同的SqlSession对象,不能用这样的代码;
因为有 private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
*/
/*SqlSession sqlSession = SqlSessionUtil.openSession();
SqlSession sqlSession1 = SqlSessionUtil.openSession();*/
SqlSessionFactoryBuilder sqlsessionfactorybuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlsessionfactorybuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
/*同一个SqlSession就走一级缓存*/
CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(13L);
System.out.println(car1);
// 手动清空一级缓存
// sqlSession.clearCache();
CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(13L);
System.out.println(car2);
}
12.3 二级缓存
@Test
public void testSelectById2() throws IOException {
// 这里只有一个SqlSessionFactory对象,二级缓存对应的就是SqlSessionFactory
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);
//这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession1是一级缓存。)
Car car1 = mapper1.selectById2(12L);
System.out.println(car1);
// 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
// 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。
sqlSession1.close();
// 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
Car car2 = mapper2.selectById2(12L);
System.out.println(car2);
// 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中
// sqlSession1.close();
// 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中
sqlSession2.close();
}
12.4 MyBatis集成EhCache
集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
<!--mybatis集成ehcache的组件-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<?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);
}
13. 逆向工程
<!--定制构建过程-->
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
14. MyBatis使用PageHelper
14.1 limit分页
mysql的limit后面两个数字:
第一个数字:startIndex(起始下标。下标从0开始。)
第二个数字:pageSize(每页显示的记录条数)
假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?
startIndex = (pageNum - 1) * pageSize
标准的mysql分页SQL
select
*
from
tableName ......
limit
(pageNum - 1) * pageSize, pageSize
<mapper namespace="com.hnlg.mapper.CarMapper">
<select id="selectByPage" resultType="Car">
select * from t_car limit #{startIndex},#{pageSize}
</select>
</mapper>
public interface CarMapper {
/**
* 通过分页的方式获取Car列表
* @param startIndex 页码
* @param pageSize 每页显示记录条数
* @return
*/
List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}
@Test
public void testSelectByPage() {
// 获取每页显示的记录条数
int pageSize = 2;
// 显示第几页,页码
int pageNum = 1;
// 计算开始下标
int startIndex = (pageNum - 1) * pageSize;
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByPage(startIndex, pageSize);
cars.forEach(System.out::println);
sqlSession.close();
}
14.2 PageHelper插件
1. 引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
2. mybatis-config.xml中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
3. 编写Java代码
@Test
public void testSelectAll() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
/*在执行DQL语句之前,开启分页功能*/
PageHelper.startPage(1, 5);
List<Car> cars = mapper.selectAll();
// cars.forEach(System.out::println);
// 封装分页信息对象new PageInfo()
// PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息对象
PageInfo<Car> carPageInfo = new PageInfo<>(cars, 2);
System.out.println(carPageInfo);
/*
PageInfo{pageNum=1, pageSize=5, size=5, startRow=1, endRow=5, total=6, pages=2,
list=Page{count=true, pageNum=1, pageSize=5, startRow=0, endRow=5, total=6, pages=2, reasonable=false, pageSizeZero=false}
[Car{id=1, carNum='100', brand='宝马520', guidePrice=41.0, produceTime='2022-09-01', CarType='燃油车'},
Car{id=2, carNum='101', brand='奔驰E300L', guidePrice=54.12, produceTime='2022-08-09', CarType='新能源车'},
Car{id=3, carNum='102', brand='丰田mirai', guidePrice=40.3, produceTime='2014-10-05', CarType='氢能源'},
Car{id=4, carNum='102', brand='丰田mirai', guidePrice=40.3, produceTime='2014-10-05', CarType='氢能源'},
Car{id=10, carNum='1001', brand='小米su7', guidePrice=10.0, produceTime='2020-10-10', CarType='新能源'}],
prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true,
navigatePages=2, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
*/
sqlSession.close();
}
15. MyBatis注解式开发
public interface CarMapper {
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
}
public class AnnotationTest {
@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();
}
}
15.2 不开启驼峰命名自动映射
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);
@Select("select * from t_car where id = #{id}")
/*解决驼峰命名自动映射的情况, id为主键*/
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "car_num", property = "carNum"),
@Result(column = "brand", property = "brand"),
@Result(column = "guide_price", property = "guidePrice"),
@Result(column = "produce_time", property = "produceTime"),
@Result(column = "car_type", property = "carType")
})
Car selectById(Long id);