1.简介
2023MyBatis 八股文------面试题_mybatis面试题八股文-CSDN博客
MyBatis用于简化JDBC的开发,一款优秀的开源的持久层框架。
( 持久层:指的是就是数据访问层(dao),是用来操作数据库的。)
**特点:1.**MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
2.MyBatis封装了几乎所有的JDBC代码和参数的手工设置以及结果集的检索;
- MyBatis使用简单的XML或注解做配置和定义映射关系,将Java的POJO
(Plain Old Java Objects(普通的旧java对象))映射成数据库中的记录。
总之:MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架。
Mybatis的优点:
1.基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在XML里,解除sql与程序代码的耦合,便于统一管理,
2.提供XML标签,支持编写动态 SQL语句
3.MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数 据库MyBatis都支持
4.能够与Spring很好的集成
缺点:
1.SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底 有一定要求
2.SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
ORM是什么?
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。
Mybatis是半自动ORM映射工具:
因为Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成。
- JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
1.数据库链接对象创建、释放频繁 造成系统资源浪费从而影响系统性能,如果使用数据库连接 池可解决此问题。
解决:在mybatis-config.xml中配置数据库链接池,使用连接池管理数据库连接。
2.Sql语句写在代码中造成代码不易维护 ,实际应用sql变化的可能较大,sql变动需要改变 java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3.向sql语句传参数麻烦 ,因为sql语句的where条件不一定,可能多也可能少,占位符需 要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。
4.对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记 录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。
**2.**MyBatis快速入门
1.在数据库中建表
2.创建模块,导入坐标
在pom.xml文件中,添加下面的三个 dependency子标记。
<!--
dependencies : 所有的依赖,用于存储各种dependency子标记
dependency子标记: 用于配置jar包的依赖坐标的:
groupId: jar包的基础包名
artifactId: 基础包名后面的项目名称
version: jar包的版本号
scope: jar包的作用域
-->
<dependencies>
<!-- @test注解测试方法 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql的依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
</dependencies>
3.编写Mybatis核心配置文件(mybatis-config.xml )。
(1)在src.main下先创建一个资源包resources,在resources下创建mybatis-config.xml 文件
(2)创建jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_db?serverTimezone=Asia/Shanghai&useTimezone=true&useSSL=false
jdbc.username=root
jdbc.password=mmforu
(3)配置mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis的核心配置文件
核心配置文件中的子标签,必须按照以下的顺序进行配置,否则报错
子标签的书写顺序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
objectWrapperFactory?,reflectorFactory?,plugins?,
environments?,databaseIdProvider?,mappers?
-->
<configuration>
<!-- 引入properties形式的文件 : resource属性用于指定文件的路径 -->
<properties resource="jdbc.properties"/>
<!-- 开启mybatis的日志组件功能 ,可以打印sql语句 ,若出错方便找错 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--起别名-->
<typeAliases>
<!-- 使用扫描包的方式,来给实体类起别名,默认自己的类名,也不区分大小写 -->
<package name="com.mybatis.pojo"/> <!--实体类所在的包,从src下开始-->
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据源的driver属性: value:引入properties文件里的value值 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 用于指定所有的Sql映射文件 : 注册Sql映射文件 -->
<mappers>
<!-- 如果mapper的映射配置越来越多,我们可以使用扫描包的功能来配置 -->
<package name="com.mybatis.mapper"/> <!--实体接口所在的包,从src下开始-->
</mappers>
</configuration>
4.编写实体类
5.编写实体接口 (类名Mapper)
5..编写sql映射文件 (类名Mapper.xml)
在resources下创建创建多层文件夹目录和接口所在的包一样(com.mybatis.mapper),创建多层文件夹时用/隔开。
在com.mybatis.mapper目录下创建 类名Mapper.xml文件(StudentMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace="实体接口的全限定名"-->
<mapper namespace="com.mybatis.mapper.EmployeeMapper">
<!--
有的时候: 字段名称和属性名称,如果相同,可以自动映射成功,进行赋值, 即字段的值赋值给属性。
但是有的时候,名称不一致,则不能自动映射成功。此时,我们可以自己定义映射关系。
resutlMap: 用于自定义映射关系的
id: 这个映射关系的唯一标识
type: 用于指定具体的实体类型
小贴士: 如果不想编写映射关系, 书写sql语句时,可以使用列别名,列别名必须和实体类的属性名相同。
-->
<resultMap id="employeeMap" type="employee">
<!-- id子标签,专门用于主键映射
culumn 属性: 用于指定表的列名
property属性:用于指定实体类的属性名
-->
<id column="empno" property="id"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="bonus"></result>
<result column="deptno" property="deptno"></result>
</resultMap>
<!-- resultMap属性:用于指定使用哪一个映射关系,书写映射关系的id值
List<Employee> findAll(): 方法返回的是集合类型, resultType书写集合的元素类型即可。
id="实体接口中的方法名"
resultType="employee" 由于在配置文件中起了别名,类别名默认是实体类的类名,不区分大小写 ;若没有在配置文件中起别名,则resultType="类的全限定名"
resultMap属性:用于指定使用哪一个映射关系
-->
<select id="findAll" resultType="employee" resultMap="employeeMap">
select * from emp
</select>
<!--
#{}: 在底层转义时,会被翻译成?形式。 与PreparedStatement的用法一样。传值时,?会被值所代替。
字符串形式的实际参数,在传递过来后,会自动带上单引号。 而且没有SQL注入风险。
${}: 就是一个普通的拼接。 如果需要单引号,需要自己拼接, 有SQL注入风险。
-->
<select id="findById" resultType="employee" resultMap="employeeMap">
select * from emp where empno= ${abvcdf}
</select>
<select id="findByName" resultType="employee" >
<!-- ${} : 如果是字符串,需要自己拼接单引号, 字符串类型或者日期类型都需要拼接单引号-->
<!-- select * from emp where ename = '${asdfkjaskdfj}'-->
<!--如果不想编写映射关系, 书写sql语句时,可以使用列别名,列别名必须和实体类的属性名相同。-->
select empno id,ename,job,mgr,hiredate,sal,comm bonus,deptno from emp where ename = #{ename}
</select>
<!--
parameterType: 用于指定参数的类型, 可以书写别名:
Integer类型的别名: int integer
int类型的别名: _int _integer Int
Map类型的别名: map
HashMap : hashmap
List : list
ArrayList : arraylist
Object : object
String : string
-->
<delete id="delEmployee" parameterType="Int">
delete from emp where empno = #{id}
</delete>
<!-- void addEmployee(Employee e);
接口里的方法带有参数,在此处就应该指定parameterType : 如果参数是实体类, 那么站位符的名字必须和实体类的属性名相同 -->
<insert id="addEmployee" parameterType="employee">
insert into emp values (null,#{ename},#{job},#{mgr},#{hiredate},#{sal},#{bonus},#{deptno})
</insert>
<update id="updateEmployee" parameterType="employee">
update emp set
ename = #{ename},
job = #{job},
mgr = #{mgr},
hiredate = #{hiredate},
sal = #{sal},
comm = #{bonus},
deptno = #{deptno}
where empno = #{id}
</update>
</mapper>
5.编写测试代码:
以EmployeeTest作为参考:
public class EmployeeTest {
private EmployeeMapper employeeMapper;
private SqlSession sqlsession;
@Before //在执行@Test下的方法之前执行
public void testBefore() throws IOException {
//第一步:加载核心配置文件, 相对于resources文件夹而言 获取SqlSessionFactory工厂对象
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
/**
*
* Mybatis:在进行增删改操作时,会主动开启事务。
*
* openSession(): 默认就开启了JDBC事务, 因此在事务真正结束后,应该手动提交。
*
* 简化方式:调用openSession(boolean AutoCommit),传入true,表示事务自动提交,不需要手动调用了
*
* 建议:使用无参方式, 可以将多个增删改方法当做一个事务进行处理。
*/
// sqlsession = factory.openSession(true);
// 第二步:获取SqlSession会话对象
sqlsession = factory.openSession();
//第三步: 获取Mapper代理对象: 让mybatis帮助我们创建接口的实现类型,我们向上造型
employeeMapper =sqlsession.getMapper(EmployeeMapper.class);
}
@Test
public void testFindAll() {
// 第四步: 使用会话对象,调用相关方法
List<Employee> all = employeeMapper.findAll();
all.forEach(System.out::println);
sqlsession.close();
}
@Test
public void testFindById(){
Employee employee = employeeMapper.findById(7369);
System.out.println(employee);
sqlsession.close();
}
@After // After注解的方法在Test注解方法执行后执行
public void testclose(){
//第五步: 关闭会话
sqlsession.close();
}
}
总结:Mybatis的编程步骤:
- 创建SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession
- 通过sqlsession执行数据库操作
- 调用session.commit()提交事务 (增删改操作)
- 调用session.close()关闭会话
编写mybatis框架的顺序
1.先在数据中建表
2.写实体类
3.编写实体类接口
4.创建配置文件
5.创建映射文件
6.编写测试类
3.多条件的查询
1.有多个参数的情况:即需要按多个字段进行查询
在映射文件里实现该方法的子标签中不用书写parameterType属性,会默认封装成Map对象,并从中寻找对应的值给占位符赋值。
根据接口中的方法的形参顺序,形参从左到右依次是arg0,arg1,arg2 .....
或者param1,param2,param3....
即Sql语句在书写时必须按照方法形参 对应的 顺序 进行书写。
**方法:**Employee findByDeptnoAndMgrAndEname(int deptno, int mgr, String ename);
sql语句:select * from emp where deptno = #{arg0} and ename = #{arg2} and mgr = #{arg1}
2.换一个思路:将多个参数封装到Map对象上,即方法中把Map对象作为形参(在映射文件中parameterType="Map"),如此sql语句中的占位符的名称必须是map的key,所以在封装对象时可以把表的字段名作为key。
方法:Employee findByMap(Map<String, Object> map);
映射文件中的代码:
<select id="findByMap" resultType="employee" parameterType="map" resultMap="employeeMap">
select * from emp where deptno = #{deptno} and mgr = #{mgr} and empno =#{empno} and job = #{job}
</select>
测试类中的测试代码:封装Map对象
public void testFindByMap(){
//自定义一个map类型,用于封装浏览器传过来的各种值。
Map<String, Object> map = new HashMap<>();
//7782 CLARK MANAGER 7839 1981-06-09 2450 10
map.put("deptno", 10);
map.put("mgr", 7839);
map.put("empno", 7782);
map.put("job","MANAGER");/* map在存储键值对时,key必须和站位符中的名字一样。 因为站位符是通过key来从map中获取具体的value值,给?赋值 */
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.findByMap(map);
System.out.println(emp);
}
3.还可以将方法中的形参(根据一些字段名从数据库中查找结果)封装成一个实体类对象,
把这些形参作为类的属性编写实体类(其中只需要书写属性,无参和全参构造器)。(在映射文件中parameterType="类名(所编写的类)")
SQL语句中的占位符是所编写的类的属性。
查询:select * from emp where empno = ? and ename = ? and job = ? and mgr = ?
封装一个参数实体类,专门用于传参用的, 比如定义一个类型 ParamsType
方法:Employee findByEntity(ParamsType params);
映射文件中的代码:
<select id="findByEntity"
resultType="employee"
resultMap="employeeMap"
parameterType="paramsType">
select * from emp where empno = #{empno} and ename = #{ename} and job = #{job} and mgr = #{mgr}
</select>
4.使用注解@Param("占位符的名字"),这种方式:就可以使用形参给对应的站位符赋值了。
方法:Employee findByAnnotation(@Param("empno") int empno,
@Param("ename") String ename,
@Param("job") String job1,
@Param("mgr") int managerno);
映射文件:
<select id="findByAnnotation" resultType="employee" resultMap="employeeMap">
select * from emp where empno = #{empno} and ename = #{ename} and job = #{job} and mgr = #{mgr}
</select>
4.可能用到的方法(分页查询):
方法:
List<Student> findByPage(@Param("offset") int offset, @Param("pageSize") int pageSize);
映射文件中的代码:
<select id="findByPage" resultType="Student">
select * from student limit #{offset},#{pageSize}
</select>
分页查询更简单的方法:
1.在pom.xml文件中配置依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2.在核心配置文件( mybatis-config.xml) 中设置分页插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
3.方法调用的思路:
1.开启分页功能
PageHelper.startPage(pageNum(当前页的页码),pageSize(一页有几条记录)); //可以获取分页的简单信息
包含的一些信息 pageNum(当前页码), pageSize(一页有几条记录) ,startRow(对于当前页码来说是从第几条记录开始查询的), endRow(下一页开始的记录index), total(数据库中有几条记录), pages=(总共有多少页)
pageNum(当前页的页码):可以从浏览器中获取。
2.想要获取关于分页更多的信息,需要在查询后,使用PageInfo类型
PageInfo< > pageInfo = new PageInfo(List list ,navigatePages);
在上面的信息的基础上加上两个: prePage(当前页的前一页), nextPage(当前页的下一页),
list:查询到的所有记录集合
navigatePages:设置一开始显示的页码
演示:
用注解的写法:
方法:
@Select("select empno id,ename,job,mgr,hiredate,sal,comm bonus, deptno from emp")
List<Employee> findAll();
测试方法:
public void testfindAll(){
PageHelper.startPage(3,5);
List<Employee> employees = mapper.findAll();
//获取分页的更多信息
PageInfo<Employee> pageInfo = new PageInfo<>(employees, 3);
//可以把pageInfo绑定在请求对象上传给浏览器
System.out.println(employees);
}
联级查询
1.多对一查询的思想:
第一种方法:在主表映射的实体类中加入一个副表映射的实体类对象属性
比如在Employee类中加入Dept 类型的属性
在映射文件文件中直接用字段映射写法:
<!-- 字段映射写法: column指定表的字段名 property指定对应的属性,
由于要给属性dept赋值,而dept是一个Detp类型,因此实际上是给dept的各个属性赋值。 -->
<resultMap id="employeeMap1" type="employee">
<id column="empno" property="id"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="bonus"></result>
<result column="deptno" property="deptno"></result>
<result column="deptno" property="dept.deptno"/>
<result column="dname" property="dept.dname"/>
<result column="loc" property="dept.loc"/></resultMap>
第二种:使用association(级联,关联,联合)标签
property: 用于指定给实体类的哪个属性做映射。
javaTyep: 用于指定该属性的java类型
association的子标记,用于将字段与实体类的属性的属性进行映射关系。
<resultMap id="employeeMap2" type="employee">
<id column="empno" property="id"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="bonus"></result>
<result column="deptno" property="deptno"></result>
<association property="dept" javaType="Dept">
<result column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
</association></resultMap>
第三种写法:分布写法:把主表中查询到的结果的某一个字段作为第二个查询语句的条件
比如查询根据员工编号查询员工信息及其所在部门信息,
第一个查询语句根据员工编号查询员工信息
select * from emp where empno = #{id}
第二个查询语句根据第一个查询语句得到的结果中的部门编号查询部门信息
select * from dept where deptno = #{id}
与主表相关的映射文件中的字段映射:
<resultMap id="employeeMap3" type="employee">
<id column="empno" property="id"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="bonus"></result>
<result column="deptno" property="deptno"></result>
<!--
多对一的第三种写法:分步写法,也使用association标签。不用再写association里面的子标签。
只需要配置下面几个属性即可:
property: 指定实体类的关联属性(Dept dept)
select: 对应的是第二步查询,语法结构namespace.id (id为select标签中写方法的属性)
column: 第二步的sql需要一个条件,column用于指定第一步查询中要作为第二个sql语句的字段名
-->
<association property="dept" fetchType="eager" select="com.mybatis.mapper.DeptMapper.findByIdSecondStep" column="deptno"/>
</resultMap>
2.一对多查询的思路
查询某一个部门的信息及其所有员工信息
准备工作:在主表映射的实体类中加入一个副表映射的实体类对象属性
第一种写法: 使用collection标签
<resultMap id="deptMap1" type="Dept">
<result column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
<collection property="employees" ofType="employee">
<id column="empno" property="id"></id>
<result column="ename" property="ename"></result>
<result column="job" property="job"></result>
<result column="mgr" property="mgr"></result>
<result column="hiredate" property="hiredate"></result>
<result column="sal" property="sal"></result>
<result column="comm" property="bonus"></result>
<result column="deptno" property="deptno"></result>
</collection>
</resultMap>
第二种写法:分布写法
第一个SQL语句根据部门编号查询部门表的信息,(主表相关的接口和映射文件)
第二个sql语句根据部门编号查询员工信息(副表相关的接口和映射文件)
与主表相关的映射文件:
<resultMap id="deptMap2" type="Dept">
<result column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
<collection property="employees" select="com.mybatis.mapper.EmployeeMapper.findByStepSecondStep" column="deptno"> </collection>
</resultMap>
涉及到三表的查询
查询每个学生的基本信息,及其所学科目信息, 以及每个学科的成绩
在主表的映射实体类中加入另外两表的映射实体类对象,作为属性。
sql语句:
select s.*,sc.*,c.*
from student s
left join score sc on s.id = sc.s_id
left join course c on sc.c_id = c.c_id
第一种方法:使用collection标签映射字段,(即有两个collection标签)
第二种方法:查询学生的sid,name,gender ,age,cid,cname,score信息
思想:把需要查询的字段作为属性封装到一个实体类(StudentInfo)中,
方法:List<StudentInfo> findAllOther();
延迟加载:
延迟加载,就是在使用数据时,进行查询操作,不使用时,不提前加载。可以节省内存,提高查询效率。
第一种方式:在映射文件里进行局部配置
再分步查询的关联标签里配置属性:fetchType="lazy" (lazy: 延迟加载 eager: 不延迟加载)
比如:<association property="dept" fetchType="eager" .....
第二种方式:全局配置(核心配置文件)
<settings>
<!--全局的延迟加载开关, value设置为true,表示开启全局延迟加载, 不写value属性,默认就是false-->
<setting name="lazyLoadingEnabled" value="true"/> //开启全局延迟加载,可以关联查询标签association里把设置属性 fetchType="eager" ..即可关闭延迟加载
</settings>
开启延迟加载后,就是如果使用的是第一个查询语句查询出的结果,则不会再向下加载。
注解完成增删改查
演示:字段和属性名不相同时,给字段起别名(即把属性作为别名)
@Select("select empno id,ename,job,mgr,hiredate,sal,comm bonus, deptno from emp")
List<Employee> findAll();
@Select("select empno id,ename,job,mgr,hiredate,sal,comm bonus, deptno from emp where empno=#{id}")
Employee findbyId(int id);//@Param("empno")
@Insert("insert into emp values (null,#{ename},#{job},#{mgr},#{hiredate},#{sal},#{bonus},#{deptno})")
void addEmployee(Employee employee);
@Update("update emp set ename=#{ename} ,job=#{job},mgr=#{mgr},hiredate=#{hiredate}," + "sal=#{sal},comm=#{bonus},deptno = #{deptno} where empno=#{id}")
void updateEmployee(Employee employee);
@Delete("delete from emp where empno=#{id}")
void delEmployee(int id);
5.动态SQL
1.where/if标签
浏览器在查询操作时,从多个条件中进行任意个组合条件进行查询,条件个数未知
配合where标签的写法, where标签的作用,用来连接条件, 如果在第一个条件前有多余的连接符号(and,or),会自动去掉
用户有的时候,按照 员工编号查询....
有的时候 按照 员工姓名查询.....
有的时候 按照 员工编号 和 职位查询......
方法:Employee findByConditionNum(Map<String, Object> map);
映射文件:
<select id="findByConditionNum" resultType="Employee" parameterType="map" resultMap="employeeMap">
select * from emp
<where>
<if test="empno !=null and empno != ' ' ">
and empno = #{empno}
</if>
<if test="ename !=null and ename != ' ' ">
and ename = #{ename}
</if>
<if test="job !=null and job != ' ' ">
and job = #{job}
</if>
</where>
</select>
调用方法的思想:把浏览器传过来的两个参数封装到Map对象上,作为形参传入。
注意封装对象时,map的key必须和占位符的名字一样。
2.choose/when标签:只能执行其中一个分支
需求: 用户可能按照员工编号查询,也可能按照员工姓名查询,也可能按照奖金查询。
方法:Employee findByConditionOne(Map<String, Object> map);
映射文件:
<select id="findByConditionOne" resultType="Employee" resultMap="employeeMap" parameterType="Map">
select * from emp where
<choose>
<when test="empno !=null and empno !='' ">
empno = #{empno}
</when>
<when test="ename !=null and ename !='' ">
ename = #{ename}
</when>
<otherwise>
comm = #{comm}
</otherwise>
</choose>
</select>
调用方法的思想:把浏览器传过来的参数封装到Map对象上,作为形参传入。
注意封装对象时,map的key必须和占位符的名字一样。
3.set/if标签:
虽然Set标签可以自动去掉最后一个字段后面的逗号,但是在书写时,必须要在字段后书写逗号。
浏览器在修改信息时,可能修改了员工姓名,职位
也可能修改了员工的工资和奖金, 总之就是修改的内容不一定是什么。
方法:void updateEmployee(Map<String,Object> map);
映射文件:
<update id="updateEmployee" parameterType="Map">
update emp
<set>
<if test="ename !=null and ename !='' ">
ename = #{ename},
</if>
..................//该表中除了主键外的所有字段都要写
<if test="deptno !=null and deptno >0 ">
deptno = #{deptno},
</if>
</set>
where empno = #{empno}
</update>
调用方法的思想:把浏览器传过来的所有参数封装到Map对象上,作为形参传入。
注意封装对象时,map的key必须和占位符的名字一样。
4.foreach标签
一个字段多个值的查询:比如查询员工编号分别为7369,7499,7782的员工信息
方法1:List<Employee> findByOneColumnMuitlValue1(String empnos);
映射文件:
<select id="findByOneColumnMuitlValue1"
resultType="Employee"
parameterType="string"
resultMap="employeeMap">
select * from emp where empno in (${empnos})
</select>
调用方法的思想:把浏览器传过来的参数的拼接成字符串,比如"7369,7499,7782"
" 'SCOTT','JAMES' "
foreach标签: 用于遍历集合参数的。 通常用于(not)in 这种sql语句。
属性:
-
collection : 写方法的形参。
-
open: 写集合的开始符号,比如"("
-
close: 写集合的开始符号,比如")"
-
item: 遍历期间的元素的存储位置,即变量(用来接收集合中的元素的值) 注意:foreach标签中要使用变量进行获取值。
-
separator: 用于指定集合的元素的分隔符
方法2: List<Employee> findByOneColumnMuitlValue2(List<String> enames);
映射文件:
<select id="findByOneColumnMuitlValue2" resultType="Employee" resultMap="employeeMap">
select * from emp
where ename in
<foreach collection="enames" open="(" close=")" item="ename" separator=",">
#{ename} <!--{}中为item属性的名字-->
</foreach>
</select>
方法调用的思想:把浏览器传过来的数据添加到集合中,作为参数传入。
6.特殊SQL的查询
1.模糊查询
比如名字的模糊查询等情况
方法:List<Student> findByLikeName(String shortname);
<select id="findByLikeName" resultType="com.mybatis.pojo.Student">
select * from student where name like '%${shortname}%'
<!-- select * from student where name like concat("%",#{shortname},"%")-->
<!-- select * from student where name like "%"#{shortname}"%"-->
</select>
调用方法的思想:将浏览器传送过来的数据作为参数传入。
2.批量删除
一般都是通过唯一标识符来删除的, 浏览器上应该使用的是复选框。
方法1:void deleteBatch1(String ids);
<!-- 批量删除1:-->
<delete id="deleteBatch1" parameterType="string" >
delete from student where sid in (${ids})
</delete>
调用方法的思想:把浏览器传过来的参数的拼接成字符串,形如"1,2,3,4"
或者" 'lucy','lisi','lily' "
注意:必须手动提交事物,调用commit()方法。
方法2: void deleteBatch2(List<Integer> ids);
<!-- 批量删除2:-->
<delete id="deleteBatch2" parameterType="list" >
delete from student where sid in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</delete>
调用方法的思想:把浏览器传过来的参数添加到集合中,作为参数传入
3.动态指定表名
方法:List<Student> findAllByTableName(String tableName);
映射文件:
<select id="findAllByTableName" resultType="com.mybatis.pojo.Student">
select * from ${tablaName}
</select>
4.返回主键
需求:向数据库中插入一条数据,插入后使用该条记录的自增主键的值。
之前的方法,需要再从数据库中查询一次才能使用,现在可以再插入完后就可以直接访问主键的值。
应用场景: 向数据库保存记录时,同时返回这条记录的主键值。应用于后续代码。
useGeneratedKeys: 是否要使用数据库中生成的该条记录的主键值, true表示使用,false表示不使用
keyProperty: 用于指定生成的主键值,存储的位置。 一般指的是对应的实体类的属性
方法:void addStudent(Student student);
映射文件:
<insert id="addStudent" parameterType="student" useGeneratedKeys="true" keyProperty="id">
insert into student (id,name,gender,age,address)values (null,#{name},#{gender},#{age},#{address})
</insert>
7.Mybatis的缓存
缓存(cache):提前把数据存放到缓存当中,下一次再使用的时候,直接从缓存中拿,而不用再次去数据库中查询一次了。这样的优势在于:通过减少IO的⽅式,来提⾼程序的执⾏效率。
MyBatis的缓存:将select语句的查询结果放到缓存(内存)当中,下⼀次还是这条相同select语句的话,直接从缓存中取,不再查数据库。⼀⽅⾯是减少了IO,另⼀⽅⾯不再执⾏繁琐的查找算法;效率⼤⼤提升。
mybatis缓存包括:
-
⼀级缓存:将查询到的数据存储到SqlSession中。
-
⼆级缓存:将查询到的数据存储到SqlSessionFactory中。
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。
1.一级缓存
一级缓存的范围是SqlSession
-
⼀级缓存默认是开启的,不需要做任何配置。
-
原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存。(即第二次查询时不会再去数据库中查询)
什么时候不走缓存
-
第⼀种:不同的SqlSession对象。
-
第⼆种:查询条件变化了。
什么时候缓存失效
-
①第⼀次查询和第⼆次查询之间,执行了clearCache() 方法,⼿动清空了⼀级缓存。
-
②第⼀次查询和第⼆次查询之间,执⾏了增、删、改操作 。(执⾏增、删、改操作时要进行事务提交,否则不生效( Mybatis:在进行增删改操作时,会主动开启事务。))
2.二级缓存
⼆级缓存的范围是SqlSessionFactory对象。
使⽤⼆级缓存需要具备以下⼏个条件:
1.再核心配置文件中开启或关闭任何缓存
<setting name="cacheEnabled" value="true"/>//默认就是true,⽆需设置(默认二级缓存就是开启的)!
2.在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加一个标签:<catche />
<cache eviction="FIFO" blocking="" readOnly="true" size="1000" flushInterval="1000" ></cache>
eviction:指定从缓存中移除某个对象的淘汰算法。(默认采⽤LRU策略)
flushInterval:⼆级缓存的刷新时间间隔,单位毫秒(刷新了也会使原来的缓存失效)。如果没有设置,就代表不刷新缓存,只要内存⾜够⼤,⼀ 直会向⼆级缓存中缓存数据,除⾮执⾏了增删改。
size:设置⼆级缓存中最多可存储的java对象数量,默认值1024。
readOnly:
true: 只读缓存,多条相同的sql语句执⾏之后返回的对象是共享的同⼀个,性能好。但是多线程并发可能会存在安全问题。
false:读写缓存,多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般,但安全。
3.使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
4.SqlSession对象提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中;此时⼆级缓存才可⽤。(注意,一级缓存还存储数据呢,并没有清空)
二级缓存失效:只要两次查询之间出现了增、删、改操作,⼆级缓存就会失效。【当然⼀级缓存也会失效】!
缓存查询顺序:
-
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
-
如果二级缓存没有命中,再查询一级缓存
-
如果一级缓存也没有命中,则查询数据库
-
SqlSession提交之后,一级缓存中的数据会写入二级缓存