1、Mybatis
1.1、MyBatis 简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一款优秀的半自动化的基于ORM思想开发的持久层框架;
ORM(Object Relational Mapping)对象关系映射:
- 类和表对应:一张表要对应一个实体类。
- 类中的属性和表字段对应:表中的字段要在实体类中有所体现。
- 类的对象和表的一条记录对应:表中的每一条记录,对应到Java中就是一个实体类的实例。
中文官网:mybatis.org/mybatis-3/z…/
1.2、MyBatis 基础配置
1)导入mybatis依赖
<!-- MyBatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- 数据库驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
2)编写持久层接口
public interface UserMapper {
// 查询表中总条数
int countAll();
}
3)编写持久层接口的映射文件(代替接口的实现类)
<?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">
<!-- 映射接口中的方法 -->
<mapper namespace="com.yang.mapper.UserMapper">
<select id="countAll" resultType="int">
SELECT count(1) FROM athinfo;
</select>
</mapper>
4)编写MyBatis配置文件
名字没有要求,但是要见名知意,例如:mybatis-con
<?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">
<configuration>
<!--引入数据源信息-->
<properties resource="jdbc.properties"/>
<!--配置环境-->
<environments default="development">
<environment id="development">
<!-- jdbc事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<!--配置mapper.xml文件 ,产生一个mapper的代理对象 -->
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
</configuration>
5)编写测试类
public static void main(String[] args) throws IOException {
// 加载核心配置文件,mybatisConfig的配置文件
InputStream stream = Resources.getResourceAsStream("MybatisConfig.xml");
// 转换成sqlsession对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
// 通过sqlSession对象来获取到代理对象
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 打印对象
System.out.println(mapper.getClass());
// 调用方法
int i = mapper.countAll();
System.out.println("查询数据有:" + i);
}
扩展
在设置中可以添加xml模板,用于直接创建mybatis的xml的头信息
注意,需要添加mapper、config两个xml配置文件
1)config头信息
<?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">
2)mapper头信息
<?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">
1.3、dao层的方法参数对sql的注入(Mybatis的参数绑定)
1)单参数绑定
当dao层接口中方法只有只有一个参数时,可以在mapper映射文件中使用*#{}*表达式,添加方法名称就可以对sql注入值
// 根据id来查找数据,对sql字段值进行绑定
Student queryById(int id);
在mapper中对方法中的参数进行对应的字段相绑定
resultType属性表示为,该方法的返回值类型
parameterType属性表示为该方法的参数类型,可以省略不写
<!--当方法只有一个参数时,直接通过#{}的形式来对sql进行字段注入-->
<select id="queryById" resultType="com.yang.entity.Student">
SELECT * FROM athinfo WHERE id = #{id};
</select>
测试,在test目录下进行对方法调用执行sql。
- 通过SqlSessionFactoryBuilder对象中build获取SqlSessionFactory
- 通过SqlSessionFactory对象中openSession方法获取SqlSession对象
- 通过SqlSession对象中的getMapper方法获取对应的映射对象
// 当持久层方法中的参数只有一个时
@Test
public void test01() throws Exception {
// 加载核心配置文件,mybatisConfig的配置文件
InputStream stream = Resources.getResourceAsStream("MybatisConfig.xml");
// 转换成sqlsession对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
// 通过sqlSession对象来获取到代理对象
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 打印对象
System.out.println(mapper.getClass());
// 调用方法
Student student = mapper.queryById(100);
System.out.println(student);
}
2)多参数绑定
当dao层接口方法中有多个参数时,在mapper映射文件中使用一下方式进行sql的注入:
- 可以使用#{arg0}、#{arg1}、#{arg2}等等来一次类推,对应的是方法中的参数顺序从下标为0开始。
- 也可以使用#{param1}、#{param2}、#{param3}等等一次类推,也是对应着方法中的参数,从1开始。
- 也可以通过注解@Param注解来对方法参数进行起别名,在与sql字段向对象匹配。
// 查找根据name和sid来进行查找
Student queryName(@Param("name") String username,@Param("sid") String StudentSid);
在mapper文件中,对方法参数进行绑定
<!--当方法中参数有多个时,可以使用#{arg0}、#{arg1}等等以此类推
也可以使用#{param1}、#{param2}、#{param3}来根据参数的顺序一次进行sql的注入
亦可以使用@Param注解来对方法中的参数起别名,通过#{}来匹配注解中的名称。
-->
<select id="queryName" resultType="com.yang.entity.Student">
SELECT * FROM athinfo WHERE sid = #{sid} AND name = #{name};
</select>
3)对象类型绑定
以对象的形式作为参数进行sql的绑定,在sql中,直接通过#{}来对sql字段数据值进行映射
// 以对象的形式来对sql进行注入
List<Student> queryList(Student student);
在mapper文件中,将对象注入到sql中
对应着对象中的属性名的get方法名。
<!--当方法中的参数为一个且是一个实体类,可以直接通过#{}中添加实体类中的属性名来进行sql注入-->
<select id="queryList" resultType="com.yang.entity.Student">
SELECT * FROM athinfo WHERE name = #{name} AND sex = #{sex};
</select>
4)组合类型绑定
组合类型就是以对象和多个参数组合在一起进行sql的注入
// 以对象和多个参数的组合的形式对sql进行注入
List<Student> queryList1(@Param("student") Student student,@Param("index") int pageIndex,@Param("count") int count);
将每个参数上添加@param注解,在对sql进行注入
#{student.name}:表示在student对象中调用属性进行sql注入
<!--当方法以对象和多个参数组合在一起进行sql的注入
需要在每个参数上添加@Param注解来起别名,用于sql的注入时的识别
-->
<select id="queryList1" resultType="com.yang.entity.Student">
SELECT * FROM ATHINFO WHERE NAME = #{student.name} AND SEX = #{student.sex} ORDER BY ID DESC LIMIT #{index},#{count};
</select>
5)map类型绑定
在dao层接口方法中,以map键值对来作为参数。
// 以map键值对作为参数的方式对sql进行注入
List<Student> queryMap(Map<String,Object> map);
在mapper映射文件中,是通过map的key值来进行sql的注入。
<!--当方法以map的形式来进行sql的注入
是以map的key来作为sql的注入字段
-->
<select id="queryMap" resultType="com.yang.entity.Student">
SELECT * FROM athinfo WHERE name = #{name} AND sex = #{sex};
</select>
当查询的字段为空时,会映射不上map
主要解决数据库中字段为空,没有办法映射到Map集合中的问题
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
1.4、MtBatis的配置注意事项
MyBatis的配置注意事项:
- 持久层接口中不能有重载方法(接口和映射文件对应,接口的方法名字和映射文件的SQL的 id 对应,id 是唯一的)
- 持久层映射 xml 文件必须添加 namespace 属性
- 持久层映射 xml 文件中的SQL标签的 id 值要和持久层接口中的方法名对应,包括大小写
- 持久层映射 xml 文件的所有入参类型都可以省略不写
- 持久层接口中方法的参数最好不要超过 1 个
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
1.5、MyBaits 实现增删改查
1.5.1、基于xml文件实现查询
1)查询多表单条数据
使用map集合来接收返回值,键为映射的sql字段
接口方法
// 查询 多表 单条数据
Map<String,Object> queryById(Integer id);
mapper映射文件
<!--查询多表单条数据-->
<select id="queryById" resultType="java.util.Map">
SELECT * FROM ATHINFO WHERE ID = #{id};
</select>
出现的问题
因为map映射字段值时,sql字段为null会映射不到map中。
解决问题:在MyBatis的核心文件中,配置name为:callSettersOnNulls,value=true
<!--解决sql查询的字段为空,没有办法映射到map集合中的问题-->
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
2)查询多表多条数据
通过list集合中嵌套map集合来实现多表多条数据关联
// 查询 多表 多条数据
List<Map<String,Object>> queryByName(String name);
3)查询单表多条数据
查询返回集合 List集合中添加对象来实现单表多条数据查询
4)模糊查询
sql文如何实现模糊查询? select * from table where name [not ] like '%张_'
方案一:不能防止SQL注入
// 模糊查询
List<Student> queryLikeName(String name);
mapper映射文件
<!--方式一:以${}的形式进行sql拼接的方式对sql进行注入-->
<!--MySQL、Oracle通用写法 pass的值写成: "999%' or 1=1 or '1'='2"-->
<select id="queryLikeName" resultType="com.yang.entity.Student">
SELECT * FROM ATHINFO WHERE NAME LIKE '%${name}%';
</select>
测试
List<Student> list = mapper.queryLikeName("王");
list.forEach(System.out::println);
方案二:可以防止SQL注入
public List<Map<String, Object>> queryForListMap(Student stu);
<select id="queryForListMap" resultType="map">
<!--MySQL、Oracle通用写法-->
select * from student where sname like #{sname}
</select>
Student student = new Student();
student.setSname("%张%");
方案三(*):可以防止SQL注入(推荐)
接口方法
// 模糊查询
List<Student> queryLikeName(String name);
mapper映射文件
<!--方式二:以#{}的方式进行进行sql的注入,有效的防止sql注入风险 使用concat函数来拼接参数-->
<select id="queryLikeName" resultType="com.yang.entity.Student">
SELECT * FROM ATHINFO WHERE NAME LIKE CONCAT('%',#{name},'%');
</select>
mybatis中#{}和${}两种参数注入的区别
- #{} :可以理解为 PreparedStatement ,可以预编译,以占位符的形式传参,能有效的防止SQL注入。主要操作数据,参数一般为页面输入的值;
- ${} :可以理解为 Statement ,不会预编译,以字符串拼接的形式传参,不能防止SQL注入。主要操作数据库对象,比如:根据不同的字段排序、动态表名、动态视图名等;
1.4.2、基于xml文件实现增删改
事务不会自动提交,需要手动提交;需要使用sqlSession对象来调用commit方法来提交事务。
resultType可以省略,返回值都为int;
1)添加
接口方法
// 添加数据
int insertCount(Student student);
mapper映射sql
<!--添加数据,映射着接口中的方法-->
<insert id="insertCount">
INSERT INTO ATHINFO(NAME,SID,SEX,STUDATE,MONEY)
VALUES (#{name},#{sid},#{sex},now(),#{money});
</insert>
测试类
/**
* 这是添加语句
* @throws IOException
*/
@Test
public void testDemo03() throws IOException {
InputStream stream =
Resources.getResourceAsStream("MybatisConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession();
CrudMapper mapper = sqlSession.getMapper(CrudMapper.class);
Student student = new Student();
student.setName("小王");
student.setSid("10045");
student.setSex(0);
student.setMoney(35000);
int i = mapper.insertCount(student);
sqlSession.commit();
System.out.println("结果:" + i);
}
2)添加时获取id主键的值
Oracle生成主键的策略:序列,通过调用序列生成,要优先于主SQL执行,所以要写BEFORE**MySQL生成主键的策略:数据库自增,数据插入之后,数据库自动生成,所以要写AFTER
mysql
使用keyProperty属性和useGeneratedKeys可以实现获取id的主键值。
useGeneratedKeys属性:允许 JDBC 支持自动生成主键
keyProperty属性:生成 key 对应的属性
- 如果入参是 对象,调用的是 set 方法,首字母小写的内容
- 如果入参是 Map,调用的是 put 方法,确保 key 在 Map 中不能重复
将主键的值映射在id属性上
<!--
useGeneratedKeys:允许 JDBC 支持自动生成主键
keyProperty:生成 key 对应的属性
如果入参是 对象,调用的是 set 方法
如果入参是 Map,调用的是 put 方法,确保 key 在 Map 中不能重复
-->
<!--添加数据,映射着接口中的方法 并将主键的值映射在id属性上-->
<insert id="insertCount" keyProperty="id" useGeneratedKeys="true">
INSERT INTO ATHINFO(NAME,SID,SEX,STUDATE,MONEY)
VALUES (#{name},#{sid},#{sex},now(),#{money});
</insert>
测试
/**
* 这是添加语句
* @throws IOException
*/
@Test
public void testDemo03() throws IOException {
InputStream stream =
Resources.getResourceAsStream("MybatisConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = build.openSession();
CrudMapper mapper = sqlSession.getMapper(CrudMapper.class);
Student student = new Student();
student.setName("小王王");
student.setSid("10042");
student.setSex(1);
student.setMoney(38000);
int i = mapper.insertCount(student);
sqlSession.commit();
System.out.println("插入后id的值:" + student.getId());
System.out.println("结果:" + i);
}
Oracle
对于oracle,是需要使用selectKey标签,在标签上添加order属性、resultType属性、keyProperty属性
order:在主SQL执行前执行或者执行后执行;
- BEFORE:在主SQL执行前执行
- AFTER:在主SQL执行后执行
keyProperty:SQL值要存到哪儿?主要取决于入参的类型,结果会自动封装到入参中
- 1、入参类型是实体类:调用的是 set 方法值是实体类中对应属性的set方法去掉set之后,首字母小写的内容
- 2、入参是Map类型:调用的是 put 方法,值可以随意,确保 key 在 Map 中不能重复
<!-- 插入数据之后返回主键,常用于注册成功之后返回主键信息,SQL执行成功之后会通过keyProperty把值设置到
入参中 -->
<insert id="add">
<selectKey resultType="String" order="BEFORE" keyProperty="sno" >
select seq_student.nextval sno from dual
</selectKey>
insert into student(sno, sname, sex, age, tel, native_place, class, pass, del)
values (#{sno}, #{sname}, #{sex}, #{age}, #{tel}, #{home}, #{clazz}, #{pass}, '1')
</insert>
使用selectKey标签
在 MyBatis 中,如果你要执行插入操作并返回自增主键,可以使用 <selectKey> 标签。该标签通常与 <insert> 标签结合使用。以下是一个示例:
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
<!-- 这里是插入的 SQL 语句 -->
INSERT INTO users (username, password) VALUES (#{username}, #{password})
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
在上述示例中:
useGeneratedKeys="true"表示开启数据库自动生成主键的功能。keyProperty="id"指定了要将自动生成的主键值设置到哪个属性上。<selectKey>标签中的 SQL 语句SELECT LAST_INSERT_ID()用于获取自增主键的值order="AFTER"表示在插入之后执行,确保在插入语句执行完成后再执行获取主键的语句。
这样,当执行插入操作时,MyBatis 会执行相应的 SQL 语句,获取生成的主键值,并将其设置到对应的实体属性中。
使用 useGeneratedKeys ,局部配置和全局配置二选一。
全局配置(mybatis核心配置文件):
<settings>
<!--
允许 JDBC 支持自动生成主键,需要数据库驱动支持。
如果设置为 true,将强制使用自动生成主键。
尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。
-->
<setting name="useGeneratedKeys" value="true"/>
</settings>
3)删除
4)修改
1.5.3、基于注解实现增删改
设计初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而在 MyBatis 3 中,我们提供了其它的配置方式。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API之上。它是 XML 和注解配置的基础。注解提供了一种简单且低成本的方式来实现简单的映射语句。
不幸的是,Java 注解的表达能力和灵活性十分有限。尽管我们花了很多时间在调查、设计和试验上,但最强大的 MyBatis 映射并不能用注解来构建——我们真没开玩笑。而 C# 属性就没有这些限制,因此MyBatis.NET 的配置会比 XML 有更大的选择余地。虽说如此,基于 Java 注解的配置还是有它的好处的。*使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。*选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML 的语句映射方式间自由移植和切换。
优点:使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
1)注册接口
将接口的类对象注册到sqlSessionFactory对象中
// MyBatis 核心对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 将接口添加到映射到sqlSessionFactory对象中
sqlSessionFactory.getConfiguration().addMapper(StudentMapperAnnotation.class);
// sqlSessionFactory.getConfiguration().addMappers("com.soft.mapper");
2)添加注解
在接口的抽象方法上添加注解
@Select("select * from student where sno = #{sno}")
public Student queryForObject(String sno);
3)增删改方法添加注解
当插入数据时,获取主键值,使用mysql的时候使用@Options注解
useGeneratedKeys:表示获取主键值,keyProperty:表示需要映射的主键值
不需要返回主key:
@Insert(
"insert into student(sno, sname, sex, age, tel, native_place, class, pass, del) values " +
"(seq_student.nextval, #{sname}, #{sex}, #{age}, #{tel}, #{home}, #{clazz}, #{pass}, '1')"
)
public int add(Student stu);
需要返回主key(mysql):
@Insert(value = "insert into cqq_tb_manager(username,password,credate) values(#{username},#
{password},now())")
@Options(useGeneratedKeys = true,keyProperty = "id")
public int insert(Manager manager);
@Update("update student set sname = #{sname} where sno = #{sno}")
public int edit(Student stu);
@Delete("delete student where sno = #{sno}")
public int del(Student stu);
1.6、 MyBatis 动态SQL
概念
1.6.1、常见动态标签
1.6.2、动态查询 if + where
条件判断,test属性为判断入参的表达式,多个表达式之间使用and、or拼接,表达式不用添加#{}
代替SQL语句中的 where 关键字,会自动去掉前面多余的连接符(and、or)
// 查询姓名和价钱,根据名称和价钱来实现动态sql
List<Student> queryByName(Student student);
mapper映射文件
方式一:
方式二:推荐
<!--动态生成if where-->
<select id="queryByName" resultType="com.yang.entity.Student">
select * from ATHINFO
<!--把where关键字替换成标签,可以去掉多余拼接符-->
<where>
<!-- 测试test中的表达式是否满足要求 -->
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="money != null and money != 0">
and money = #{money}
</if>
</where>
</select>
测试
@Test
public void test1() throws IOException {
InputStream asStream =
Resources.getResourceAsStream("MybatisConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(asStream);
SqlSession sqlSession = build.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("张三");
student.setMoney(30000);
List<Student> list = mapper.queryByName(student);
list.forEach(System.out::println);
}
1.6.3、动态插入 if + trim
接口方法,动态的插入数据
// 插入数据,使用动态sql来实现插入数据
int insertStudent(Student student);
mapper映射文件
<!--使用if trim标签来实现动态的sql插入-->
<insert id="insertStudent">
INSERT INTO ATHINFO
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">
NAME,
</if>
<if test="sid != null and sid != ''">
sid,
</if>
<if test="stuDate != null">
stuDate,
</if>
<if test="sex != null and sex != 0">
sex,
</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">
#{name},
</if>
<if test="sid != null and sid != ''">
#{sid},
</if>
<if test="stuDate != null">
#{stuDate},
</if>
<if test="sex != null and sex != 0">
#{sex},
</if>
</trim>
</insert>
测试:
@Test
public void test2() throws IOException {
InputStream asStream =
Resources.getResourceAsStream("MybatisConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(asStream);
SqlSession sqlSession = build.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("111");
student.setMoney(32000);
long time = System.currentTimeMillis();
Timestamp date = new Timestamp(time);
student.setStuDate(date);
int i = mapper.insertStudent(student);
// 提交事务
sqlSession.commit();
System.out.println("结果为:" + i);
}
1.6.4、动态更新 if + set
代替SQL语句中的set关键字,会自动去掉多余的逗号
// 跟新数据,使用动态sql实现更新sql
int updateStudent(Student student);
<!--动态实现跟新sql-->
<update id="updateStudent">
UPDATE ATHINFO
<set>
<if test="name != null and name != ''">
NAME = #{name},
</if>
<if test="sex != null and sex != 0">
SEX = #{sex},
</if>
<if test="money != null and money != 0">
MONEY = #{money},
</if>
</set>
WHERE ID = #{id};
</update>
1.6.5、选择 choose-when
选择,满足一个条件后其他条件不再判断,可以理解为if - else if - else
代码实例
// 根据一个变量值进行跟新不同的字段
int updateStudentByFlg(@Param("stu") Student student,@Param("flg") Integer flg);
<!---->
<update id="updateStudentByFlg">
UPDATE ATHINFO
<choose>
<when test="flg == 1">
name = #{stu.name},
</when>
<when test="flg == 0">
MONEY = #{stu.money},
</when>
</choose>
WHERE ID = #{stu.id};
</update>
1.6.6、批量删除 foreach
遍历,多用于批量执行collection:结果集; 1、如果入参是list、数组、map类型的可以直接写list、collection、array、map;2、或者按照参数的索引位置,arg0、arg1item:集合中的每一个对象**index:下标open:循环以某个字符开头close:循环以某个字符结尾separator:循环内容之间的分隔符,会自动去掉多余的分隔符 发音:色不瑞特儿
使用list数据作为参数,来传递根据ids来批量删除
// 动态的执行批量删除sql
int deleteStudentByIds(List<Integer> ids);
mapper映射文件
<!--这个是动态的实现批量删除sql-->
<delete id="deleteStudentByIds">
DELETE FROM ATHINFO WHERE ID IN
<foreach collection="list" item="i" open="(" close=")" separator=",">
#{i}
</foreach>
</delete>
测试
/**
* 这是批量动态执行sql
* @throws IOException
*/
@Test
public void test5() throws IOException {
InputStream asStream =
Resources.getResourceAsStream("MybatisConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(asStream);
SqlSession sqlSession = build.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 创建list数组
ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(120, 121));
int i = mapper.deleteStudentByIds(arrayList);
sqlSession.commit();
System.out.println("结果为:" + i);
}
1.6.7、批量插入 foreach
通用批量插入,使用表拷贝语句
表的拷贝语句,[where 1 != 1]表示可以省略,省略后代表赋值表的结构及其数据
creat table 新表名称 as select * from 拷贝表名称 [where 1 != 1]
表的拷贝语句,将一个结果集添加到表中。和单条数据的添加方式不同的是
insert into tableName后面不再跟values关键字,而是跟一个结果集。那么如何构建一个结果集呢?有两种情况:
1、基于数据库表查询到结果集。这个写法适用于表之间的数据拷贝。2、基于查询固定值构建结果集。这个写法适用于没有表的形式。
1)适用于mysql和Oracle的动态sql插入语句
根据固定的结果集来插入数据,想要实现结果集需要将多个语句的结果进行合并。合并结果集的关键字是union all
-- 基于查询固定值构建结果集
/*
在实际的业务需求中,要添加的数据是从页面接收,传递到持久层的,所以不能使用表的方式构建结果集,自然就要使用查询固定值的方式构建结果集。
另外,基于 Oracle 严谨的语法结构,查询时必须要保证语法的完整性,所以 from 后面要跟虚拟表 dual。MySQL 则可以省略不写。
例如:`select '张三', 18, '男', '17312341234', ... from dual`。
但是,一个查询语句只能表示一条数据,想要实现结果集需要将多个语句的结果进行合并。合并结果集的关键字是`union all`。
*/
insert into tableName(c1, c2, c3, ...)
(
select 数据1, 数据2, 数据3, ... [from dual]
union all
select 数据1, 数据2, 数据3, ... [from dual]
)
<!--
批量插入分析:
1、表的拷贝语句,从一张表中,把数据查询出来,添加到表中
insert into tb_student (
select * from tb_student_bk
)
2、【select * from tb_student_bk】查询出来的是结果集
以上的需求就可以转换为将一个结果集直接添加到表中
insert into tb_student (
结果集
)
3、结果集的构成:
3.1 查询具体的表,查询的是字段,但是这个结构并不能被批量添加所使用,因为数据不来源表,而来源于用户的提交。
用户提交的是数据。所以要考虑,如何把数据变成结果集。
select no, sex, tel from tb_student
3.2 结果集是一条一条组成的,如何把用户提交的一个一个的数据变成一条数据。
Oracle 写法:从虚拟表中查询固定的值(数据)
select '1', '男' from dual
select '2', '女' from dual
select '3', '男' from dual
MySQL 写法:直接查询数据
select '1', '男'
select '2', '女'
select '3', '男'
3.3 多条数据的合并,union all
select '1', '男' from dual
union all
select '2', '女' from dual
union all
select '3', '男' from dual
4、综上
insert into tb_student (
select '1', '男' from dual
union all
select '2', '女' from dual
union all
select '3', '男' from dual
)
4.1 结果集的分析
select #{no}, #{sex} from dual
union all
select #{no}, #{sex} from dual
union all
select #{no}, #{sex} from dual
循环体:
select #{no}, #{sex} from dual
分隔符:
union all
-->
<!--对于mysql和oracle使用的批量插入-->
<insert id="insertStudentByData">
INSERT INTO ATHINFO(NAME,MONEY,SID,SEX)
<foreach collection="list" item ="stu" separator="UNION ALL">
SELECT #{stu.name},#{stu.money},#{stu.sid},#{stu.sex} FROM DUAL
</foreach>
</insert>
2)用于mysql的批量插入语句
语法
insert into tableName(c1, c2, c3, ...) values
(v1, v2, v3, ...),
(v1, v2, v3, ...),
(v1, v2, v3, ...),
(v1, v2, v3, ...),
....
代码实例
<!--
批量插入MySQL分析:
insert into tb_student values
('', '', '', '', ''),
('', '', '', '', ''),
('', '', '', '', ''),
('', '', '', '', ''),
('', '', '', '', '')
循环体:
('', '', '', '', '')
分隔符:
,
-->
<!--对于mysql的插入-->
<insert id="insertStudentByData">
INSERT INTO ATHINFO(NAME,MONEY,SID,SEX)
VALUES
<foreach collection="list" item="stu" separator=",">
(
#{stu.name},#{stu.money},#{stu.sid},#{stu.sex}
)
</foreach>
</insert>
3)对于Oracle来动态实现批量插入数据
Oracle 批量插入,带序列。注意:不能使用 useGeneratedKeys 主键策略,否则会出现命令未正确结束错误。
<insert id="add">
insert into tb_student(no, name, sex, tel, address, class, del_flg)
select seq_student.nextval, t.*
from (
<!-- 循环查出结果集 -->
<foreach collection="list" item="stu" separator="union all">
select
#{stu.name}, #{stu.sex}, #{stu.tel}, #{stu.address}, #{stu.clazz}, #{stu.delFlg}
from dual
</foreach>
) t
</insert>
1.6.8、sql片段映射
将出现频次高的SQL内容,提取出来,供需要者调用。减少代码的开发和修改。
- 定义SQL片段,多用于提取重复的SQL语句
- 引入SQL片段,refid是标签的id值
1)单个映射文件使用sql片段
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.soft.mapper.QueryMapper">
<sql id="query_sql">
sno, sname, sex, age, tel, native_place home, class clazz, pass, head, del
</sql>
<select id="queryForList" resultType="student">
select <include refid="query_sql"/> from student
</select>
</mapper>
2)多个映射文件使用sql片段
1、创建全局的映射文件
<?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="global">
<sql id="query_sql">sno, sname, sex, age, tel, native_place home, class clazz, pass, head, del</sql>
</mapper>
2、由于全局的映射文件没有接口映射,所以必须需要单独配置管理全局的映射文件
<!--配置映射文件-->
<mappers>
<mapper resource="com/soft/mapper/global.xml"/>
<package name="com.soft.mapper"/>
</mappers>
3、引入SQL片段,通过全局映射文件的namespace.sql标签的id
<?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">
<mapper namespace="com.soft.mapper.QueryMapper">
<select id="queryForList" resultType="student">
select <include refid="global.query_sql"/> from student
</select>
</mapper>
1.6.10、动态实现分页
mysql动态分页
使用mybatis来实现动态分页sql
<!-- Mapper XML 文件中的动态 SQL 示例 -->
<!-- 使用<if>元素判断是否存在查询条件 -->
<select id="selectUsers" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username = #{username}
</if>
</where>
<!-- 使用<if>和<choose>元素来判断排序条件 -->
<choose>
<when test="orderBy != null and orderBy == 'username'">
ORDER BY username
</when>
<when test="orderBy != null and orderBy == 'email'">
ORDER BY email
</when>
<otherwise>
ORDER BY user_id
</otherwise>
</choose>
<!-- 使用<if>元素判断是否启用分页 -->
<if test="offset != null and limit != null">
LIMIT #{offset}, #{limit}
</if>
</select>
在接口方法中,在 Java 代码中,你可以定义一个与查询条件相匹配的方法,如下所示:
@Param 注解用于为 XML 中的参数传递提供参数名称。
public interface UserMapper {
List<User> selectUsers(@Param("username") String username,
@Param("orderBy") String orderBy,
@Param("offset") Integer offset,
@Param("limit") Integer limit);
}
Oracle动态分页
在 Oracle 中,实现动态分页通常使用 ROWNUM 或 FETCH FIRST ... ROWS ONLY(Oracle 12c 及更高版本支持)的方式。以下是一个示例,展示如何在 MyBatis 中实现动态分页:
xmlCopy code
<!-- 在 Mapper XML 文件中的动态分页示例 -->
<select id="selectUsers" resultType="User">
SELECT * FROM (
SELECT
users.*,
ROWNUM AS rnum
FROM
(SELECT * FROM users ORDER BY user_id) users
WHERE
ROWNUM <= #{end}
)
WHERE
rnum > #{start}
</select>
在这个示例中,ROWNUM 用于实现分页。#{start} 和 #{end} 是 MyBatis 传递的参数,用于计算分页的开始和结束位置。
在 Java 代码中,你可以定义一个与查询条件相匹配的方法,如下所示:
javaCopy code
public interface UserMapper {
List<User> selectUsers(@Param("start") int start,
@Param("end") int end);
}
在这个例子中,start 和 end 参数表示分页的起始位置和结束位置。请注意,这是一种常见的实现方式,但具体的分页方式可能根据 Oracle 版本和需求的不同而有所变化。
对于嵌套子查询对数据排序,会导致效率降低,可任意使用基于游标的分页或者使用ROW_NUMBER()函数结合子查询来实现动态分页。
1.7、mysql的其他设置及注意
1.7.1 MyBatis 配置文件
1.7.1.1 数据源配置
<!--
数据库环境的集合,可以配置多个数据源,通过default配置默认数据源
项目开发过程中有开发环境和生产环境,在开发过程中使用开发环境,项目上线时修改default可以切换为生产环境
default:默认使用的数据库环境
-->
<environments default="mysql">
<!--
单个的数据库环境
id:当前数据库的标识
-->
<!--MySQL环境:-->
<environment id="mysql">
<!--事务管理器,使用JDBC的事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源配置,使用连接池-->
<dataSource type="POOLED">
<!--驱动、url、账号、密码-->
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
<!--Oracle环境-->
<environment id="oracle">
<!--事务管理器,使用JDBC的事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源配置,使用连接池-->
<dataSource type="POOLED">
<!--驱动、url、账号、密码-->
<property name="driver" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</dataSource>
</environment>
</environments>
1.7.1.2 自定义类型起别名
自定义类型在映射文件中需要写类的全限定名,而Java内置类型则不需要,是因为MyBatis底层分装了这些类型,我们可以给自定义类型起名字。
配置别名的过程可能会出现
Caused by: java.lang.ClassNotFoundException: Cannot find class: xxxx,类找不到的错。
- 1、别名没有配置。
- 2、配置了别名但名字写错了。
使用typeAliases标签在mybatis核心文件进行对实体类进行起别名。
<typeAliases>
<!-- 给单个实体类起别名 -->
<typeAlias type="com.soft.entity.Student" alias="student"/>
</typeAliases>
<typeAliases>
<!-- 扫描实体类路径给所有的实体类起别名,名称为类名或者类名首字母小写 -->
<package name="com.soft.entity"/>
</typeAliases>
上述扫描包后,也可以通过注解直接标注在实体类上进行命别名。(注意必须还需有上述扫描实体类路径。)
1.7.1.3 扫描映射文件
在核心配置文件中
<!--配置映射文件-->
<mappers>
<!--
name:xml文件所在目录,使用/隔开,因为xml是资源文件,不是java文件。所以找文件值需要通过目录找,而不是包名
1、xml和持久层接口必须在同一个目录下(代码编译之后在一个包下,不要看IDE结构)
2、xml的文件名和持久层接口的文件名必须一致包含大小写
-->
<package name="com.soft.mapper"/>
</mappers>
1.7.1.4 控制台SQL日志
mybatis核心配置文件中:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
1.7.2 无效列类型:1111
在mabatismapper映射文件中字段为输入参数为 null 会发生的问题(MyBatis 空指针异常)
Oracle 数据库可能会出现的问题。输入参数为 null 会发生的问题(MyBatis 空指针异常)
org.apache.ibatis.type.TypeException: Error setting null for parameter #1 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property.
解决方法,
给映射文件的入参添加jdbcType:org.apache.ibatis.type.JdbcType枚举类
<!--
给映射文件的入参添加jdbcType:org.apache.ibatis.type.JdbcType枚举类
jdbcType的值要全大写
-->
<select id="queryForObject2" resultType="student">
select * from student where sno = #{sno, jdbcType=VARCHAR} and pass = #{pass, jdbcType=VARCHAR}
</select>
Cause: java.sql.SQLException: 无效的列类型: 1111
Caused by: org.apache.ibatis.type.TypeException: Error setting null for parameter #1 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 无效的列类型: 1111
| Jdbc数据类型 | Java数据类型 |
|---|---|
| CHAR | String |
| VARCHAR | String |
| NUMBERIC | BigDecimal |
| BOOLEAN | boolean |
| INTEGER | int |
| DOUBLE | double |
| SMALLINT | short |
| VARBINARY | byte[] |
| DATE | java.sql.Date |
| TIME | java.sql.Time |
| TIMESTAMP | java.sql.Timestamp |
1.7.3 返回值为Map映射为空
主要解决数据库中字段为空,没有办法映射到Map集合中的问题
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
1.7.4 #和$的区别
#{}:可以理解为PreparedStatement,可以预编译,以占位符的形式传参,能有效的防止SQL注入。主要操作数据,参数一般为页面输入的值;${}:可以理解为Statement,不会预编译,以字符串拼接的形式传参,不能防止SQL注入。主要操作数据库对象,比如:根据不同的字段排序、动态表名、动态视图名等;
1.7.5、视图解析器
在springmvc配置文件中来配置,相当于全局的请求路径
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="otherViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/path/to/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
控制层
@Controller
public class ExampleController {
@RequestMapping("/example")
public String example(Model model) {
model.addAttribute("message", "Hello, World!"); // 添加名为"message"的模型数据
return "example"; // 使用viewResolver解析名为"example"的JSP文件
}
@RequestMapping("/other")
public String other(Model model) {
model.addAttribute("message", "Hello, Other!"); // 添加名为"message"的模型数据
return "forward:/path/to/jsp/other.jsp"; // 使用otherViewResolver解析位于"/path/to/jsp/"目录下的"other.jsp"文件
}
}
1.8、高级映射
高级映射主要解决字段和属性对应不上和多表关联查询的问题。
表之间的关联关系有:一对一,一对多,那如何描述表之间的关系,就需要用到高级映射了。
实体类和DB结构是一一对应的。单表的结果对应一个对象。多表的结果怎么对应?
主要是解决查询的字段和实体类的属性对应不上并且关于多表关联查询时的问题,是用来表述表之间的关系,也是实体类之间的关系。
1.8.1、单表映射
单表映射主要解决在单个表中字段和实体类属性不能对应的问题。
标签及其属性详解:
- id标签表示映射着sql主键,
- result表示着映射着非主键字段;
- property属性表示实体类中的成员属性;
- column表示着sql语句中的字段;
- javaType表示实体类中的成员属性类型;
- jdbcType表示sql语句中的字段类型;
方式一:起别名
在查询sql时,给字段起别名,使字段能够映射上实体类属性上。
方式二:resultMap
在mapper映射文件中的查询中,添加resultMap属性,然后再添加resultMap标签,在标签中一对一映射实体类属性。
代码实例:
实体类
public class Student {
private String no;
private String name;
private String sex;
private String tel;
private String address;
private String clazz;
private String delFlg;
// 省略set/get方法
}
Dao接口
/**
* 单表的映射
*/
Student queryForObject(Student student);
映射文件
<?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">
<mapper namespace="com.soft.dao.ResultMapDao">
<!--
id:resultMap的唯一标识
type:完整的Java类或者别名
-->
<resultMap id="rm1" type="student">
<!--
column:需要映射的字段名,以sql查询结果的字段名为准
property:需要把字段映射到实体类对应的属性(实体类中属性的set方法名称)
jdbcType:数据库中字段对应的类型
javaType:java中属性的类型
-->
<result column="no" property="no" javaType="String" jdbcType="VARCHAR"/>
<result column="name" property="name" javaType="String" jdbcType="VARCHAR"/>
<result column="sex" property="sex" javaType="String" jdbcType="VARCHAR"/>
<result column="tel" property="tel" javaType="String" jdbcType="VARCHAR"/>
<result column="address" property="address" javaType="String" jdbcType="VARCHAR"/>
<result column="class" property="clazz" javaType="String" jdbcType="VARCHAR"/>
<result column="del_flg" property="delFlg" javaType="String" jdbcType="VARCHAR"/>
</resultMap>
<select id="queryForObject" resultMap="rm1">
select
no, name, sex, tel, address, class, del_flg
from
tb_student
where
no = #{no}
</select>
</mapper>
1.8.2、多表映射:一对一关系
需要让表与表之间实现一对一之间的关系,通过关联的方式来让实体类中存在另一个实体类对象作为属性=
使主实体类中添加副实体类的对象作为属性。
一对一关系中标签及其属性详解:
- association:用于描述一对一的关系,用于映射副实体类中的属性; 耳骚kei深
- property:主实体类中的属性
- javaType:当前属性对应的Java中的类型(实体类)
副实体类
create table tb_major(
mno varchar2(20),
mname varchar2(100)
);
insert into tb_major(
select 'M10001', '软件工程' from dual
union all
select 'M10002', '土木工程' from dual
union all
select 'M10003', '信息工程' from dual
union all
select 'M10004', '电竞专业' from dual
union all
select 'M10005', '美术' from dual
union all
select 'M10006', '体育' from dual
);
实体类
public class Student {
private String no;
private String name;
private String sex;
private String tel;
private String address;
private String clazz;
private String delFlg;
// 专业的对象
private Major major;
// 省略set/get方法/toString方法
}
public class Major {
private String mno;
private String mname;
// 省略set/get方法/toString方法
}
Dao接口
/**
* 多表映射:学生和专业
* 一个学生对应一个专业:一对一的关系
*
* 从表结构中,学生表中有专业的专业号
*/
Student queryForObjectWithMajor(Student student);
映射文件在mapper映射文件中,使用resultMap来手动实现sql字段和实体类字段相映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.soft.dao.ResultMapDao">
<!--
一个学生对应一个专业
在学生的角度去维护专业信息
要在学生的实体类中去体现专业信息
-->
<resultMap id="rm2" type="student">
<!--学生信息-->
<result column="no" property="no" javaType="String" jdbcType="VARCHAR"/>
<result column="name" property="name" javaType="String" jdbcType="VARCHAR"/>
<result column="sex" property="sex" javaType="String" jdbcType="VARCHAR"/>
<result column="tel" property="tel" javaType="String" jdbcType="VARCHAR"/>
<result column="address" property="address" javaType="String" jdbcType="VARCHAR"/>
<result column="class" property="clazz" javaType="String" jdbcType="VARCHAR"/>
<result column="del_flg" property="delFlg" javaType="String" jdbcType="VARCHAR"/>
<!--专业信息-->
<!--
association:用于描述一对一的关系
property:主实体类中的属性
javaType:当前属性对应的Java中的类型(实体类)
-->
<association property="major" javaType="Major">
<result column="mno" property="mno" javaType="String" jdbcType="VARCHAR"/>
<result column="mname" property="mname" javaType="String" jdbcType="VARCHAR"/>
</association>
</resultMap>
<select id="queryForObjectWithMajor" resultMap="rm2">
select
t1.no, t1.name, t1.sex, t1.tel, t1.address, t1.class, t1.del_flg,
t2.mno, t2.mname
from
tb_student t1
left join
tb_major t2
on t1.mno = t2.mno
where
t1.no = #{no}
</select>
</mapper>
1.8.3、多表关联:一对多的关系
在实体类中添加副实体类对象作为属性,由于对象之间是一对多的关系,副实体类会映射多条数据,需要使用list<副实体类>来接收数据。
在mapper文件中使用ResultMap标签来手动映射字段参数属性。
一对多标签和属性详解:
- collection:用于维护一对多的关系。如果多的一方数据有重复(每个字段的值都一样)会进行合并。一定要通过id给定主键关系,是入参参数的结果集类型
- property:属性名称
- ofType:集合依赖的泛型的类型
实体类:
public class Order {
private Integer id;
private String oCode;
private String oName;
private BigDecimal oPriceMoney;
// 引入副实体类对象,在sql映射时需要对应上副实体类属性
// 因为关联的副实体类产生对条数据,使用list来接受
private List<OrderDetail> orderDetail;
// 省略setget方法
}
副实体类
public class OrderDetail {
private Integer orderDetailId;
private Integer orderId;
private String shopName;
private BigDecimal price;
// 省略setget方法
}
接口
public interface OrderMapper {
// 这是根据订单id来查找订单数据和订单详情
List<Order> queryOrderList(Order order);
}
mapper映射文件
<?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">
<mapper namespace="com.yang.mapper.OrderMapper">
<resultMap id="orderMapping" type="com.yang.entity.Order">
<!--id标签表示映射着sql主键,
result表示着映射着非主键字段;
property属性表示实体类中的成员属性;
column表示着sql语句中的字段;
javaType表示实体类中的成员属性类型;
jdbcType表示sql语句中的字段类型;
-->
<id property="id" column="id" javaType="Integer" jdbcType="INTEGER"/>
<result property="oCode" column="orderCode" javaType="String" jdbcType="VARCHAR"/>
<result property="oName" column="orderName" javaType="String" jdbcType="VARCHAR"/>
<result property="oPriceMoney" column="countMoney" javaType="java.math.BigDecimal" jdbcType="NUMERIC"/>
<!--
collection:用于维护一对多的关系。如果多的一方数据有重复(每个字段的值都一样)会进行合并。一定要通过id给定主键关系
property:属性名称
ofType:集合依赖的泛型的类型
-->
<collection property="orderDetail" ofType="com.yang.entity.OrderDetail">
<id property="orderDetailId" column="orderdetailId" javaType="Integer" jdbcType="INTEGER"/>
<result property="orderId" column="orderId" javaType="Integer" jdbcType="INTEGER"/>
<result property="shopName" column="shopName" javaType="String" jdbcType="VARCHAR"/>
<result property="price" column="shopMoney" javaType="java.math.BigDecimal" jdbcType="NUMERIC"/>
</collection>
</resultMap>
<!--根据订单号来查找订单数据和订单详情数据-->
<select id="queryOrderList" resultMap="orderMapping">
SELECT t1.id, t1.orderCode, t1.orderName, t1.countMoney,t2.id orderdetailId
, t2.orderId, t2.shopName, t2.shopMoney
FROM `order` t1
LEFT JOIN orderdetail t2
ON t1.id = t2.orderId
WHERE t1.id = #{id};
</select>
</mapper>
测试
@Test
public void test1() throws IOException {
// 加载mybatis核心文件
InputStream asStream =
Resources.getResourceAsStream("mybatisConfig.xml");
// 创建SqlSessionFactory对象
SqlSessionFactory build =
new SqlSessionFactoryBuilder().build(asStream);
// 获取SqlSession对象
SqlSession sqlSession = build.openSession();
// 获取mapper接口的代理对象
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
// 执行方法
Order order = new Order();
order.setId(1);
List<Order> orderList = mapper.queryOrderList(order);
orderList.forEach(System.out::println);
}
1.9、MyBatis 存储过程
存储过程属于数据库编程的范畴。一个业务逻辑中,可能需要多次频繁的调用数据库,会对数据库带来压力。可以将逻辑写到数据库中的存储过程中,通过调用存储过程实现功能,减少了调用数据库的次数,减轻了数据库压力。
Oracle:
/*
创建存储过程,通过学号返回学生的电话和姓名
*/
create procedure pro_query_student(
i_no in varchar2, -- 输入参数:学号
o_tel out varchar2, -- 输出参数:电话
o_name out varchar2 -- 输出参数:姓名
) as
begin
select
tel, name
into
o_tel, o_name
from
tb_student
where
no = i_no;
end;
MySQL
/*
创建存储过程,通过学号返回学生的电话和姓名
*/
create procedure pro_query_student(
in i_no varchar(255), -- 输入参数:学号
out o_tel varchar(255), -- 输出参数:电话
out o_name varchar(255) -- 输出参数:姓名
)
begin
select
tel, name
into
o_tel, o_name
from
tb_student
where
no = i_no;
end;
/**
* 存储过程特点:存储过程没有返回值,只有输入参数和输出参数
* 根据以上特点:接口的方法也不需要返回值,存储过程的输出结果会封装到入参对象中(入参必须是对象,不能是字面量类型,因为值传递和值引用的问题)
*/
void queryForObject(Student student);
<?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">
<mapper namespace="com.soft.dao.ProDao">
<!--
调用存储过程,根据学号查询学生的电话、姓名
输入参数:学号
输入参数:电话、姓名
statementType:SQL执行器的类型
statement:执行普通SQL,不能防止SQL注入
prepared:执行普通SQL,可以防止SQL注入
callable:执行存储过程或者函数
useCache:使用缓存;false是不使用缓存,每次执行的时候都从数据库查询最新的
mode:指定参数是输入还是输出参数。in和out
-->
<select id="queryForObject" useCache="false" statementType="CALLABLE">
{call pro_query_student(
#{no, mode=IN, jdbcType=VARCHAR},
#{tel, mode=OUT, jdbcType=VARCHAR},
#{name, mode=OUT, jdbcType=VARCHAR}
)}
</select>
</mapper>
@Test
public void testQueryPro() {
Student student = new Student();
student.setId(100);
System.out.println(student);
mapper.queryPro(student);
// 执行存储过程后的out参数结合属性名称自动封装到student对象中
System.out.println(student);
}
1.10、MyBatis 配置日志
日志就是将代码在执行过程中的状态或者结果进行展示的技术。通过日志可以分析代码执行的异常情况和执行状况。日志一般都是系统维护人员去看。日志一般输出在控制台和日志文件中。
导入log4j的依赖jar包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1.10.1、log4j 日志的组成部分(核心)
1、日志级别:日志信息详细程度
- 由高到低:DEBUG -> INFO -> WARN -> ERROR
- 级别越高日志的内容越多
- 高级别的日内容包含低级别日志内容
2、日志的输出目的地(Appender):日志信息展示的地方
- 控制台:org.apache.log4j.ConsoleAppender
- 文件:org.apache.log4j.RollingFileAppender、org.apache.log4j.DailyRollingFileAppender
- 邮件:org.apache.log4j.net.SMTPAppender
- 数据库:org.apache.log4j.jdbc.JDBCAppender
3、日志的布局(layout):日志信息的内容格式,可以理解为日志的要素(时间地点任务的关系)
- %p:日志级别
- %c:类名
- %m:信息
- %n:换行
- %d:日期{yyyy-mm-dd hh:mm:ss}
1.10.2、log4j配置文件
log4j.properties 文件编写,注意文件名固定
#####################################################
# 日志就是功能,只要是功能,就需要类和对象,以及方法调用 #
#####################################################
# 日志目的地和全局日志的级别,
# 本文中设定的是全局的DEBUG,,为的是开发过程中方便查看调试信息
# 日志输入目的地设置了4个,分别是控制台,文件,邮件,数据库
# 全局的变量定义:日志级别和日志目的地
log4j.rootLogger=ERROR,console,logFile,jdbc,mail
#################################控制台打印#################################
# appender:目的地,一个appender就是一个目的地
# 配置目的地对象:控制台对象
# ConsoleAppender console = new ConsoleAppender();
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 日志打印的类别,err:错误信息,字体颜色为红色;out:打印信息,
log4j.appender.console.target=System.err
# 自定义当前appender的日志级别:DEBUG -> INFO -> WARN -> ERROR
log4j.appender.console.threshold=DEBUG
# 配置控制台信息的布局
log4j.appender.console.layout=org.apache.log4j.PatternLayout
# %p:日志级别
# %c:类名
# %m:信息
# %n:换行
# %d:日期{yyyy-MM-dd hh:mm:ss}
log4j.appender.console.layout.conversionPattern=[%p] %d{yyyy-MM-dd hh:mm:ss} %c : %m%n
################################# 输出到文件 #################################
# 配置目的地对象:文件对象
# log4j.appender.logFile=org.apache.log4j.RollingFileAppender
# 配置文件大小
# log4j.appender.logFile.MaxFileSize=50MB
# file表示文件路径
# 可以是相对路径也可以是绝对路径
# log4j.appender.logFile.File=myLog.log
# 配置文件信息的布局
# log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
# log4j.appender.logFile.layout.conversionPattern=[%p] %d{yyyy-MM-dd hh:mm:ss} %c : %m%n
# -----------------------------------------------------------------------
log4j.appender.logFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logFile.File=myLog.log
# 配置时间
log4j.appender.logFile.datePattern=yyyy-MM-dd
# 配置文件信息的布局
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.conversionPattern=[%p] %d{yyyy-MM-dd hh:mm:ss} %c : %m%n
################################# 输出到数据库 #################################
# 配置目的地对象:数据库对象
log4j.appender.jdbc=org.apache.log4j.jdbc.JDBCAppender
# 配置数据库相关信息
log4j.appender.jdbc.Driver=oracle.jdbc.driver.OracleDriver
log4j.appender.jdbc.URL=jdbc:oracle:thin:@localhost:1521:orcl
log4j.appender.jdbc.user=
log4j.appender.jdbc.password=
# SQL语句
log4j.appender.jdbc.sql=insert into tb_log values(seq_log.nextval, '%c', '%m', sysdate)
# 自定义当前appender的日志级别
log4j.appender.jdbc.threshold=INFO
# 配置数据库的信息布局
log4j.appender.jdbc.layout=org.apache.log4j.PatternLayout
################################# 输出到邮件 #################################
# java发送邮件需要导jar包:javax.mail
# 配置目的地对象:邮件对象
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
# 配置发送邮件的信息
# 发件人登录邮箱的账号,一般为邮箱
log4j.appender.mail.SMTPUsername=
# 发件人登录邮箱的密码,三方软件授权码
log4j.appender.mail.SMTPPassword=
# 发件人邮箱
log4j.appender.mail.From=
# 发件人邮箱服务器,smtp.163.com
log4j.appender.mail.SMTPHost=
# 发件人邮箱服务器端口,25
log4j.appender.mail.SMTPPort=
# 邮件主题
log4j.appender.mail.Subject=
# 收件人邮箱
log4j.appender.mail.To=
# 配置邮件的布局,邮箱使用HTML布局
log4j.appender.mail.layout=org.apache.log4j.HTMLLayout
# 单独针对某个包设置打印级别
log4j.logger.com.soft=DEBUG
1.10.3、MyBatis配置文件添加log4j配置
<settings>
<!--固定写法-->
<setting name="logImpl" value="log4j"/>
<!--下述配置:主要查看sql文-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
1.10.4、自定义log4j日志
通过定义全局的log4j对象,
- 在方法执行前输入日志:方法正在执行;
- 在方法执行后添加日志:方法执行完毕;
- 再方法出现异常在trycath中输出日志:方法出现异常。
1.11、Mybatis的缓存机制
MyBatis 中的缓存是一种机制,用于提高数据库访问性能,通过在内存中存储查询结果,减少对数据库的频繁访问。MyBatis 提供了两级缓存:一级缓存(本地缓存)和二级缓存(全局缓存)。
当多次执行相同的查询时,可以启用缓存,提高查询效率
禁用缓存
如测试sql语句性能时缓存会影响测试准确性 需要禁用在映射文件中:默认值是true useCache=”false”
<select id="findAllPets" resultMap="petsMap" useCache="false">
select * from pets
</select>
刷新缓存
在映射文件中:属性:flushCache=”true”刷新缓存,在查询语句中,默认值是false,在新增删除修改语句中,默认值是true(清空缓存)
1.11.1、一级缓存
一级缓存是 MyBatis 中的本地缓存,也称为 SqlSession 级别的缓存。它是在同一个 SqlSession 内部有效,用于缓存在执行数据库操作期间产生的结果。
概念
MyBatis的一级缓存默认是开启,不需要手动配置。MyBatis的一级缓存存在于SqlSession的生命周期中,在同一个SqlSession中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键,将键和查询结果存入一个 Map 对象中。
如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法会生成相同的键,当 Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。也就是说 MyBatis 判断调用的方法和参数不变时会从缓存中取值。
如果在调用完查询操作之后,把数据库中的数据修改;再次调用查询操作,参数不变。那么会调用一次数据库,此时的结果就跟数据库的结果不对应。如何解决这个问题呢?可以在xml映射文件的查询操作添加【 flushCache=“true” 】即可。
简述
mybatis的一级缓存是作用于sqlsession对象,在同一个sqlsession对象中查询时,mybatis会将执行的方法和参数作为缓存的key保存到缓存对象中,当再一次执行相同的sql和参数时,会直接返回缓存中的值,不会直接查询数据库。当第一次查询后,对表中数据进行了修改,会刷星缓存,这是再次执行形同的sql时会直接走数据库。
特点
- 作用范围: 一级缓存的作用范围是在同一个 SqlSession 内有效。当在同一个 SqlSession 中执行相同的查询语句时,MyBatis 将会从缓存中直接返回结果,而不再向数据库发起查询。
- 默认开启: 一级缓存是默认开启的,也就是说,每个新的 SqlSession 都会有一个新的缓存。
- 生命周期: 一级缓存的生命周期与 SqlSession 的生命周期一致。当 SqlSession 关闭时,一级缓存中的数据也会被清空。
- 工作原理: 在执行查询语句时,MyBatis 会将查询结果放入一级缓存。如果在同一个 SqlSession 中再次执行相同的查询,MyBatis 将首先检查一级缓存,如果找到匹配的结果,直接返回缓存的结果,而不再执行实际的数据库查询。
- 清空缓存: 一级缓存可以手动清空,可以通过
clearCache()方法清空当前 SqlSession 的缓存。也可以在执行增删改操作时,MyBatis 会自动清空相应的缓存。 - 线程安全性: 一级缓存是与线程绑定的,每个线程拥有自己的 SqlSession 和一级缓存。不同线程之间的缓存不会相互影响。
- 懒加载影响: 使用懒加载(
lazyLoadingEnabled="true")时,一级缓存可能因为需要加载关联对象而触发额外的查询。
执行流程
- 第一次sql查询用户id信息,先去缓存中查询是否有,如果没有,从数据库中查询用户信息,得到用户信息后在将用户信息储存到一级缓存中。
- =如果sqlSession去执行commit操作(插入、更新、删除),清空sqlSession中的一级缓存,保证缓存中始终保存的是最新的信息,避免脏读。
- 第二次执行相同的sql查询用户id信息,先去缓存中查询,如缓存中有,直接从缓存中获取。
注意:两次查询须在同一个sqlsession中完成,否则将不会走mybatis的一级缓存。
一级缓存配置
mybatis一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION。STATEMENT
如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。
如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,在下通过localCacheScope指定。
<!-- 表示所有mapper执行的sql语句都不会走缓存,只会走数据库 -->
<setting name="localCacheScope" value="STATEMENT"/>
代码详解
接口抽象方法
public interface StudentMapper {
// 根据id来查找学生信息
List<Student> queryByStudentId(Student student);
// 根据id来修改学生信息
int updateStudentById(Student student);
}
映射文件
<mapper namespace="com.yang.mapper.StudentMapper">
<!--根据id查找学生信息-->
<select id="queryByStudentId" resultType="student">
SELECT * FROM ATHINFO WHERE ID = #{id}
</select>
<!--根据id来修改学生信息-->
<update id="updateStudentById">
UPDATE ATHINFO SET NAME = #{name},SEX = #{sex},SID = #{sid}
WHERE ID = #{id}
</update>
</mapper>
测试类
当第一次sql查询后,如果进行了数据的修改,mybatis会刷新缓存,下次相同的sql查询时会直接执行数据库。
/**
* 这是用于测试一级缓存
* @throws IOException
*/
@Test
public void test2() throws IOException {
// 加载mybatis核心文件
InputStream asStream =
Resources.getResourceAsStream("mybatisConfig.xml");
// 创建SqlSessionFactory对象
SqlSessionFactory build =
new SqlSessionFactoryBuilder().build(asStream);
// 获取SqlSession对象
SqlSession sqlSession = build.openSession();
// 获取mapper接口的代理对象
// 创建多个mapper对象,都共享sqlSession对象
StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession.getMapper(StudentMapper.class);
// 通过多个mapper对象来执行相同的sql,参数和执行方法相同。
Student student = new Student();
student.setId(100);
student.setName("8888");
student.setSex(1);
student.setSid("8080");
System.out.println("------创建mapper1会话,执行sql-----");
List<Student> students = mapper1.queryByStudentId(student);
students.forEach(s -> System.out.println(s));
// 执行修改操作。会刷新缓存
int i = mapper1.updateStudentById(student);
// 提交事务
sqlSession.commit();
// 当第一次执行sql后会将执行的方法和参数作为key保存到缓存中(缓存是一个map集合),
// 将sql查询到的结果集封装成对象保存到value中
// 再次执行相同的sql时,会先走缓存中,查看缓存是否由对应的key,没有才会执行数据库。
System.out.println("------创建mapper2会话,执行sql-----");
List<Student> students1 = mapper2.queryByStudentId(student);
students1.forEach(s -> System.out.println(s));
System.out.println(mapper1 == mapper2);
}
1.11.2、二级缓存
概念
二级缓存是 MyBatis 中的全局缓存,它的作用域范围是包含多个 SqlSession对象。
MyBatis 一级缓存最大的共享范围就是同一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要使用二级缓存。当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 => 一级缓存 => 数据库。
实现二级缓存的时候,MyBatis要求返回的POJO(实体类)必须是可序列化的。开启二级缓存的条件也是比较简单,通过直接在 MyBatis 配置文件配置,还需要在映射文件中添加<cache/>标签。
特点
-
作用范围: 二级缓存的作用范围是在多个 SqlSession 之间有效。当多个 SqlSession 执行相同的查询语句时,如果该查询的结果已经存在于二级缓存中,MyBatis 将会从缓存中直接返回结果,而不再向数据库发起查询。
-
需要配置: 二级缓存需要在 MyBatis 配置文件中显式配置启用。
<!-- 配置开启全局缓存 --> <setting name="cacheEnabled" value="true"/> -
生命周期: 缓存的生命周期与整个应用程序的生命周期一致。当整个应用程序关闭时,二级缓存中的数据才会被清空。
-
工作原理: 在执行查询语句时,MyBatis 会将查询结果放入二级缓存。如果在不同的 SqlSession 中再次执行相同的查询,MyBatis 将首先检查二级缓存,如果找到匹配的结果,直接返回缓存的结果,而不再执行实际的数据库查询。
-
清空缓存: 二级缓存可以手动清空,可以通过配置文件中的
<cache-ref namespace="otherNamespace"/>来引用其他命名空间的缓存。也可以在执行增删改操作时,MyBatis 会自动清空相应的缓存。 -
线程安全性: 二级缓存是全局共享的,不同线程之间的缓存可以相互影响。为了保证线程安全,MyBatis 使用了锁机制。
-
配置优化: 可以通过配置文件中的
<cache/>标签来配置缓存的策略和属性,以优化缓存的性能和行为。
二级缓存的设计有助于在多个 SqlSession 之间共享缓存,减少数据库访问次数,提高整体性能。
实现流程
1、在配置文件中 开启二级缓存的总开关
<setting name="cacheEnabled" value="true" />
2、 在mapper映射文件中开启二级缓存
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
参数名属性eviction收回策略 flushInterval刷新间隔 size引用数目 readOnly只读
关于eviction的各个参数属性:
- 参数名属性eviction="LRU"最近最少使用的:移除最长时间不被使用的对象。
- (默认)eviction="FIFO"先进先出:按对象进入缓存的顺序来移除它们。
- eviction="SOFT"软引用:移除基于垃圾回收器状态和软引用规则的对象。
- eviction="WEAK"弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
3、当映射的方法返回值有实体类时,实体类实现Serializable
4、在最后测试类中需要关闭sqlSession对象
代码实现
实体类
需要实现序列化接口
public class Student implements Serializable {// 实现序列接口
private Integer id;
private String sid;
private String name;
private Integer sex;
private Timestamp stuDate;
private Integer money;
// 省略getset方法
}
接口抽象方法
public interface StudentMapper {
// 根据id来查找学生信息
List<Student> queryByStudentId(Student student);
// 根据id来修改学生信息
int updateStudentById(Student student);
}
映射文件
<mapper namespace="com.yang.mapper.StudentMapper">
<!--在映射文件中开启二级缓存-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<!--根据id查找学生信息-->
<select id="queryByStudentId" resultType="student">
SELECT * FROM ATHINFO WHERE ID = #{id}
</select>
<!--根据id来修改学生信息-->
<update id="updateStudentById">
UPDATE ATHINFO SET NAME = #{name},SEX = #{sex},SID = #{sid}
WHERE ID = #{id}
</update>
</mapper>
测试类
创建多个sqlSession对象,使用sqlsession对象来创建sql会话查询,共享SqlSessionFactory对象。
注意,需要在方法执行完毕后需要关闭SqlSession对象,才能够将查询到的结果集放在缓存中。=
/**
* 二级缓存测试
* @throws IOException
*/
@Test
public void test3() throws IOException {
// 加载mybatis核心文件
InputStream asStream =
Resources.getResourceAsStream("mybatisConfig.xml");
// 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(asStream);
// 获取SqlSession对象
// 创建多个SqlSession对象,共享SqlSessionFactory对象
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
Student student = new Student();
student.setId(100);
// 通过sqlSession对象创建多个会话查询
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
List<Student> students = mapper1.queryByStudentId(student);
students.forEach(s -> System.out.println(s));
// 关闭sqlSession1,会将查到结果集保存到缓存中
sqlSession1.close();
// 通过sqlSession对象创建多个会话查询
// 当执行相同的sql执行,会先查找缓存中是否存在,不存在才会执行数据库查询。
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
List<Student> students2 = mapper2.queryByStudentId(student);
students2.forEach(s -> System.out.println(s));
// 关闭sqlSession2,会将查到结果集保存到缓存中
sqlSession2.close();
}
Mybatis一级缓存与二级缓存的区别
1)一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Myabits默认开启一级缓存。
在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。
*每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis的内部缓存是使用一个HashMap对象,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。 SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。*也清空二级缓存(执行commit()的效果)。
2) Mybatis二级缓存是默认不开启的,作用于一个Application,是Mapper级别的,多个SqlSession使用同一个Mapper的sql能够使用二级缓存。
1.11.3、Mybatis的缓存执行流程
当用户发送一个查询请求: 判断二级缓存是否存在: 存在:从缓存中读取 不存在: 判断一级缓存中是否存在数据 存在:从缓存中读取 不存在:发送查询语句到数据库查询。 一级缓存什么时候可以当作同一个查询: 一个SqlSeession可以看作一个查询; 一旦当前有增删改操作时,针对同一个mybatis会默认当作两个查询。
1.11.4、缓存的注意事项
- 映射语句文件中的所有SELECT语句将会被缓存。
- 映射语句文件中的所有INSERT、UPDATE、DELETE语句会刷新缓存。
- 缓存会使用LeastRecentlyUsed(LRU,最近最少使用的)算法来收回。
- 根据时间表(如noFlushInterval,没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用。
- 缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
1.12、mybatis的分页插件
概念
MyBatis 分页插件 PageHelper 可以让程序员专注 SQL 的编写,不用考虑不同数据库的分页语句,通过PageHelper可以自动识别数据库种类,进行分页语句的生成执行。
常用方法
-
startPage 方法: 开启分页,在查询之前调用。
// pageNum 表示第几页,pageSize 表示每页大小 PageHelper.startPage(pageNum, pageSize); -
offsetPage 方法: 设置分页偏移量,在查询之前调用。
// offset 表示偏移量,limit 表示每页大小 PageHelper.offsetPage(offset, limit); -
orderBy 方法: 设置排序规则,在查询之前调用。
// orderByClause 表示排序字段,例如 "id ASC" PageHelper.orderBy(orderByClause); -
pageSizeZero 方法: 当 pageSize 为 0 或者 RowBounds 模式时,返回全部结果(不分页)。
PageHelper.pageSizeZero(true); -
count 方法: 返回总记录数。
long total = PageHelper.count(() -> yourMapper.yourMethod(params)); -
afterPage 方法: 执行完分页查询后调用,返回分页信息。
PageInfo pageInfo = PageHelper.afterPage(); -
getPageNum 方法: 获取当前页码。
int currentPageNum = PageHelper.getPageNum(); -
getPageSize 方法: 获取每页大小。
int currentPageSize = PageHelper.getPageSize(); -
clearPage 方法: 清除分页信息。
PageHelper.clearPage(); -
自动 count 查询开关: 在配置文件中开启或关闭自动进行 count 查询。
<property name="autoRuntimeDialect" value="true"/> <property name="autoRuntimeDialect" value="false"/>
这些方法使得在进行分页查询时,无需手动编写复杂的 SQL 语句,大大简化了分页操作的流程。
实现流程
PageHelper 是一个用于 MyBatis 的分页插件,它通过拦截 SQL 语句,自动在查询语句中添加分页相关的 SQL 片段,从而简化了分页查询的操作。下面是 PageHelper 的主要实现原理:
- Interceptor 拦截器: PageHelper 是一个 MyBatis 的拦截器,它实现了
Interceptor接口。MyBatis 拦截器允许我们在执行 SQL 语句的过程中干预和修改 SQL 的执行过程。 - 拦截方法: PageHelper 主要通过拦截
StatementHandler接口的prepare方法来实现分页。prepare方法是在执行 SQL 语句之前调用的,PageHelper 在这个方法中对原始的 SQL 进行修改。 - Page 分页对象: PageHelper 使用
ThreadLocal来存储分页信息,以保证在同一个线程中分页信息能够正确传递。PageHelper 会在每个线程中创建一个Page对象,这个对象包含了分页的相关信息,如当前页码、每页大小、总记录数等。 - SQL 解析和拼接: 当 PageHelper 拦截到 SQL 语句时,它会解析原始的 SQL 语句,并根据分页信息拼接相应的 SQL 片段,通常是在原始的 SQL 语句后面添加
LIMIT和OFFSET。这样,数据库执行的实际 SQL 语句就包含了分页信息。 - 执行原始 SQL: PageHelper 在修改 SQL 后,将修改后的 SQL 传递给下一个拦截器或最终执行 SQL 的地方,继续执行原始的 SQL 操作。
- 总记录数查询: 为了获取总记录数,PageHelper 会额外执行一次查询(count 查询)来获取总记录数,并将其设置到
Page对象中。 - 返回结果: PageHelper 将处理后的 SQL 执行并返回结果,包括查询的数据和总记录数。
总的来说,PageHelper 利用 MyBatis 的拦截器机制,在 SQL 语句执行之前动态地修改 SQL,添加分页信息。通过解析和拼接 SQL,以及执行额外的 count 查询,它实现了在不修改原始查询语句的情况下,实现了分页的效果。 PageHelper 的这种实现方式简化了分页查询的开发,提高了开发效率。
使用步骤
1)导入第三方依赖jar包--》pagehelper
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
2)在MyBatis配置文件中,配置分页插件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
3)代码实现
接口抽象方法
// 根据名称或学号模糊查询学生信息
List<Student> queryByStudentNameOrId(Student student);
映射文件
<!--根据名称和学号模糊查询分页查询
分页由插件完成
-->
<select id="queryByStudentNameOrId" resultType="student">
SELECT * FROM ATHINFO
<where>
<if test="name != null and name != ''">
NAME LIKE CONCAT('%',#{name},'%')
</if>
<if test="sid != null and sid != ''">
AND SID LIKE CONCAT('%',#{sid},'%')
</if>
</where>
</select>
测试类
/**
* 这是分页
* @throws IOException
*/
@Test
public void test4() throws IOException {
// 加载mybatis核心配置文件
InputStream asStream = Resources.getResourceAsStream("mybatisConfig.xml");
// 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(asStream);
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("小");
// student.setSid("");
// 当前页数
int currentPage = 1;
// 每页显示的条数
int pageSize = 10;
// 实现分页
PageHelper.startPage(currentPage, pageSize);
// 调用方法
List<Student> students = mapper.queryByStudentNameOrId(student);
students.forEach(t -> System.out.println(t));
// 将分页查询数据,保存到PageInfo对象中
PageInfo pageInfo = new PageInfo(students);
System.out.println(pageInfo);
pageInfo.getList().forEach(System.out::println);
}
2、SSM的集成
2.1、概念
- S:springMVC(servlet、springboot、struts2)控制层框架
- S:Spring实例化对象
- M:MyBatis(hibernate)持久层对象
2.2、导入jar包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--导入springmvc的依赖jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>
<!--spring的事务管理依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.16</version>
</dependency>
<!--spring的jdbc连接数据库依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.16</version>
</dependency>
<!--mybatis的核心依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--spring和mybatis的核心集成依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--数据库驱动jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--第三方数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<!--log4j的日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--第三方分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
</dependencies>
2.3、项目结构
2.4、配置文件
2.4.1、springmvc相关配置文件
<!--配置springmvc的扫描器,实例化控制层对象-->
<context:component-scan base-package="com.yang">
<!--设置白名单,只扫描controller层对象-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--实例化spring的类型转换器-->
<!--<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!–给converters属性进行赋值 converters返回的是set集合–>
<property name="converters">
<set>
<!–实例化自定义类型转换器类–>
<bean class="com.yang.conver.StringToDateConverter" ></bean>
</set>
</property>
</bean>-->
<!--配置注解驱动器 有类型转换需要添加加载类型转换器-->
<mvc:annotation-driven>
<!-- 配置全局的中文乱码-->
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--配置springmvc的静态资源加载器-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
2.4.2、spring的相关配置文件
spring-bean的配置文件
<!--spring扫描器,实例化对象-->
<context:component-scan base-package="com.yang">
<!--设置黑名单,不扫描控制层对象-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
spring-resource的相关配置
<!--加载数据库信息-->
<context:property-placeholder file-encoding="utf-8" location="classpath:jdbc.properties"/>
<!--实例化第三方数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--加载对应的数据信息1-->
<property name="driverClassName" value="${mysql.driver}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<!--连接池功能-->
<!--设置连接池中最多支持100个绘画活动-->
<property name="maxActive" value="100"></property>
<!--设置请求连接池时常,超过maxWait值后请求失败,-1表示无限等待-->
<property name="maxWait" value="-1"/>
</bean>
<!--事务管理-->
<!--加载mybatis核心配置文件-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--加载mybatis的核心文件-->
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
<!--加载mapper映射文件-->
<property name="mapperLocations" value="classpath:com/yang/mapper/*.xml"/>
<!--加载数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--扫描mapper接口和mapper映射文件对应,产生代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描mapper接口所在路径-->
<property name="basePackage" value="com.yang.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
</bean>
2.4.3、mybatis的相关配置文件
mybatis核心配置文件
<?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">
<configuration>
<!--全局设置-->
<settings>
<!--配置打印日志-->
<setting name="logImpl" value="log4j"/>
<!--开启二级缓存-->
</settings>
<typeAliases>
<!-- 扫描实体类路径给所有的实体类起别名,名称为类名或者类名首字母小写 -->
<package name="com.yang.entity"/>
</typeAliases>
<!--配置第三方分页插件-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
</configuration>
mapper的映射文件
<?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">
<mapper namespace="com.yang.mapper.StudentMapper">
<!--根据姓名和学号模糊查询-->
<select id="queryByNameOrSid" resultType="com.yang.entity.Student">
SELECT * FROM ATHINFO
<where>
<if test="name != null and name != ''">
NAME LIKE CONCAT('%',#{name},'%')
</if>
<if test="sid != null and sid != ''">
AND SID LIKE CONCAT('%',#{sid},'%')
</if>
</where>
</select>
</mapper>
2.4.4、web文件的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--设置每次服务器打开页面-->
<welcome-file-list>
<welcome-file>query.jsp</welcome-file>
</welcome-file-list>
<!--加载web核心对象-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--启动web项目时,加载DispatcherServlet对象-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--设置加载请求时常-->
<!-- <load-on-startup>1</load-on-startup>-->
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--加载spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<!--配置监听器来加载spring文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--设置统一的编码过滤器-->
<!--配置编码格式过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--将编码格式设置为utf-8-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3、映射遇到的问题
3.1、数据库字段为联合主键
当进行一对多进行多表映射时,数据库从表的字段都为主键并且为联合主键,需要在mybatis创建两个id标签用于映射两个主键。
3.2、一对多查询并实现分页
分页
其实很简单,最终获取到的是1对多的集,limit是对最终的临时表进行分页,即对最终一对多形成的结果进行分页,而不是根据主表数据分页.
select u.*,s.* from
(select u1.* from user u1 where u1.gender='男' limit 0,3) u
left join score s on u.id=s.user_id
排序
当你使用的sql需要进行排序的时候,子查询需要排序,外面的查询也需要排序
select u.*,s.* from
(select u1.* from user u1 where u1.gender='男' order by u1.id desc limit 0,3) u
left join score s on u.id=s.user_id
order by u.id desc
嵌套子查询实现分页
<resultMap id="rm1" type="com.yang.entity.Role">
<id property="role_id" column="role01" />
<result property="name" column="NAME" javaType="String" jdbcType="VARCHAR" />
<result property="state" column="STATE" javaType="String" jdbcType="VARCHAR" />
<!--映射从表中的字段与实体类属性相映射-->
<collection property="rolePrivileges" ofType="com.yang.entity.RolePrivilege"
select="com.yang.mapper.RoleMapper.pageRole" column="role01">
<id property="rolePri_id" />
<id property="code" column="CODE" javaType="String" jdbcType="VARCHAR" />
</collection>
</resultMap>
<!-- 对主表进行分页,使用column="role01" 来关联着从表,对从表进行映射 -->
<select id="showRoleList" resultMap="rm1">
select T1.ROLE_ID role01,T1.NAME,T1.STATE from role T1
</select>
<!-- 从表关联 -->
<select id="pageRole" resultType="com.yang.entity.RolePrivilege">
SELECT T2.ROLEPRI_ID ,T2.CODE FROM ROLE_PRIVILEGE T2 where T2.ROLEPRI_ID = #{role01}
</select>
控制层
/**
* 这是查询全部角色表数据
* @param model
* @return
*/
@RequestMapping("/rolePage")
public String rolePage(Model model,@RequestParam(required = false) String currentPage,@RequestParam(required = false) String roleName){
if (currentPage == null){
currentPage = "1";
}
PageHelper.startPage(Integer.parseInt(currentPage),4);
// 查询全部角色表数据
List<Role> roles = roleService.showRoleList(roleName);
roles.forEach(t -> System.out.println("输出一下:" + t));
PageInfo pageInfo = new PageInfo(roles);
// System.out.println(pageInfo.getPages());
// 将数据存入model
model.addAttribute("roles",roles);
model.addAttribute("pageInfo",pageInfo);
return "tax/role/listUI";
}