Mybatis框架
Mybatis的含义:Mybatis框架是一个持久层框架,几乎解决了jdbc代码在手动设置参数和对结果集的手动获取问题,原本是apache公司的开源项目,最后转给Google公司。Mybatis会将参数封装在一个对象中传递给数据库,并将sql语句执行后的结果集封装成对象。
它提供全局配置文件,建立与数据库的连接;将接口进行分装并提供类和方法实现对数据库的链接和操作;对sql语句执行后的结果进行高级映射并封装成对象;支持动态sql;支持缓存。
Mybatis中存在自动映射,因为当在数据库中查询的表的属性名和类中的属性名完全相同时才会自动映射并封装,所以需要保证类中的属性和数据库中的属性名相同;如果类中的属性为私有属性,那么类中必须实现get和set方法;还需要保证类中要有无参构造方法,由于mybatis在数据库中查询数据后需要创建对象,调用对应类的无参构造方法,如果找不到对应的无参构造方法,mybatis就会报错;当数据库中的属性名存在驼峰命名,mybatis也是会进行自动映射,但前提是需要开启属性mapUnderscoreToCamelCase,这个属性需要在全局配置文件中设置,表示是否开启驼峰命名的自动映射,true为开启,开启后例如在数据库中的属性名为student_id,那么类中的属性只要设置为studentId这种驼峰命名的方式就可以进行自动映射。
搭建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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
1、在项目的pom.xml配置文件中导入mybatis的jar包
2、配置全局配置文件(数据库连接信息)
在resources目录创建一个.xml文件用来配置全局文件,并在文件中写入配置信息
其中<dataSource>标签用来配置连接数据库的配置信息,其中driver的值为com.mysql.cj.jdbc.Driver,url的值为jdbc:mysql://127.0.0.1:3306/mybatisdb?serverTimezone=Asia/Shanghai,username和password则为自己连接数据库的用户名和密码,这里我使用的数据库是SQLyog
<dataSource type="POOLED">如果type为unpooled那么表示获取连接时不是从连接池中获取,而是返回一个新建的连接;如果type为pooled
(1)⾸先先判断空闲连接池内有没有空闲连接,如果还有则给你返回⼀个空闲连接。
(2)、如果没有空闲连接,则去活动连接池内看看还有没有位置,如果还有,则new⼀个连接给你返回
(3)、如果活动连接池没有位置了,则返回在活动连接池使⽤最久的连接。意思就是给你返回⼀个在活动连接池内待最久的连接
<mappers>标签用来添加映射配置文件,一个映射添加一个<mapper>标签,标签中resource属性表示映射文件对于全局配置文件的相对地址
3、写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=""></mapper>
<mapper>标签中用来写sql语句,namespace属性的值对应和此映射文件所绑定的接口的包名,例如
这样映射文件就和接口进行了绑定,我们还需要在配置文件中获取连接数据库的信息以及构建SqlSessionFactory,由于SqlSessionFactory一旦创建就会一直存在于Mybatis的应用过程中,并且由于创建SqlSessionFactory的开销过大 ,所以我们在构建SqlSessionFactory时只需要创建一次即可,所以我们可以创建一个类并将创建SqlSessionFactory的方法放在这个类的静态代码块中,这样即使多次调用这个类但是创建SqlSessionFactory只会执行一次。
public class MyBatisUtil {
static SqlSessionFactory sqlSessionFactory = null;
static {
try {
// 将全局配置文件放入到流中
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
// 与数据库建立连接
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public SqlSession getSqlSession() {
return sqlSessionFactory.openSession();//通过SqlSessionFactory对象获取SqlSession
}
}
getSqlSession方法时用来通过SqlSessionFactory对象的openSession方法获取SqlSession对象,我们可以通过SqlSession对象来执行与参数和返回值相匹配的接口
如果向执行sql语句,首先要在接口中定义抽象方法,并确定参数列表和返回值;然后在映射文件中写对应的sql语句,如果为select语句则需要在<select>标签中书写sql语句,如果为insert语句则需要在<insert>标签中书写sql语句,update和delect语句同理。
public interface StudentDao {
Student find(int id);//查询通过id查询学生的信息
}
sql语句中的id需要与接口中的方法名相同,resultType为返回值类型这里的类型为Student,由于Student为自定义的类型,所以需要写该类型的包名,#{id}表示传过来的参数的值
<select id="find" resultType="com.ffyc.mybatisdemo.model.Student">
select * from student where id = #{id}
</select>
以下为具体实现的方法
public void findStudent() {
SqlSession sqlSession = new MyBatisUtil().getSqlSession();//通过类获取SqlSession对象
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);//通过接口的class(类)对象,获取代理对象,由于接口和映射配置文件绑定,所以可以通过代理对象调用接口中的方法
Student student = studentDao.find(1);//通过代理对象调用接口中的find方法,并传入参数1
System.out.println(sqlSession);
sqlSession.close();
}
4、测试
执行方法后的结果如下,得到如下结果需要在Student中实现toString方法
参数传递问题
与映射器绑定的接口中的方法中的参数可以是任意一个,因为映射器配置文件中的id属性的值和对应接口的方法名相同,所以同一个接口中的方法不能重名。当方法中的参数为一个时,直接将该参数进行传递即可;而当参数为多个时需要使用@Param注解对参数进行绑定,@Param里的值为类中的属性名,与其绑定的则为形参。
void find(@Param("id")Integer id,@Param("no")Integer no,@Param("name")String name);
当接口中的参数过于多时,我们可以将参数封装在一个对象中,通过传递对象来传递参数,但是我们还需要在映射器中加入parameterType属性来说明传递的参数的类型。
void find(Student student);
<select id="find" parameterType="Student"></select>
增删改查
当我们对数据库进行增添操作时,我们先把数据封装在对象中并将值传给数据库中进行操作,此时如果我们还想通过刚新插入的数据的id查询其它的内容,由于id是自增的是由数据库进行自加的,所以我们并不知道id是多少。这时我们可以通过在sql语句的<insert>标签中添加三个属性,就可以获取到数据库在对数据进行添加后的id,并将id封装在对象的属性中。useGeneratedKeys="true"表示是否开启将自增属性传回,keyColumn="id"表示数据库表中的自增属性,keyProperty="id"表示要将获取到的自增属性的值赋值给类中的哪个属性,这样能获取自增属性id的条件是id得是自增属性。
<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admin(account,password,gender)value(#{account},#{password},#{gender})
</insert>
在新增,删除和修改操作中,我们在将SqlSession关闭之前,需要将SqlSession的实例化对象的commit方法进行提交,而查询操纵则不需要。因为查询操作没有对数据库中的数据进行改变。
Mybatis中还存在增删改查操作的注解标签,一般如果某些增删改查操作的sql语句较为简单,我们就可以直接在对应的接口上面通过注解标签的形式来进行操作。
@Select("select * from grade where id = #{id}")
int selectGrade(int id);
#{}和${}的区别
#{}为占位符,通过预编译的方式先用?代替出现占位符的地方,等到将sql语句编译完成再将传入的参数,可以防止sql注入
${}为拼接符,拼接符就像字符串一样被Mybatis拼接到sql语句中,不能防止sql注入
结果处理
结果处理就是Mybatis对sql语句执行后的结果进行的封装处理,有时我们不止只在一张表中进行查询操作,返回的结果集中可能存在多个表中的数据,这时我们就需要在mapper映射器中将所有的映射关系进行配置,因为Mybatis在多表查询后时不会将结果进行自动映射并封装的。
例如在学生和年级关系中,如果想要查询一个学生的信息和其所对应的年级信息,这是一个多对一关系的关联查询,查询的结果集中存在不属于学生表的属性,这时我们需要在自定义的学生类中添加一个年级类型的属性用来存放和年级相关的所有信息,并在mapper映射器中配置各个查询结果的属性所对应类中的属性。
public class Student {
private Integer id;
private String name;
private String gender;
private Grade grade;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", no=" + no +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", grade=" + grade +
", admin=" + admin +
'}';
}
}
public class Grade {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Grade{" +
"id=" + id +
", name='" + name + '\'' +
", studentList=" + studentList +
'}';
}
}
如果查询结果集中的属性存在于多个表中,我们在查询<select>标签中的resultType属性将会变为resultMap属性,其值为<resultMap>标签中自定义的id的值,type="Student"表示返回结果集的类型,<resultMap>标签中有<id>和<result>标签,<id>标签代表数据库的表中主键属性,<result>标签则代表其他属性,column="id"表示在数据库表中的属性名,property="id"表示自定义类中的属性名。如果类中存在其他自定义类型的属性则使用<association>标签表示,其中也存在<id>和<result>标签配置方式和前面相同。我们还需要人为的在查询语句中给结果的属性列起别名,这样可以保证在配置映射关系时一个结果集的属性名可以对应一个类中的属性,这是多对一关系结果集映射关系的配置。
<resultMap id="findInfo" type="Student">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
<association property="grade" javaType="Grade">
<result column="gname" property="name"></result>
</association>
</resultMap>
<select id="findStudent" resultMap="findInfo">
SELECT s.id,s.name,s.gender,g.name gname FROM student s LEFT JOIN grade g ON s.gradeid = g.id WHERE s.id = #{id}
</select>
如果我们要查询在一个年级中有多少个学生以及每个学生的基本信息,这是一个一对多关系的关联查询,这时我们也需要在年级类中添加新的属性,由于是一对多关系所以一个年级就会对应多个学生,所以我们需要添加一个存放学生信息的集合属性。
public class Grade {
private int id;
private String name;
private List<Student> studentList;
}
配置映射关系的方式和前面大致相同,仍然是写在一个<resultMap>标签中,当属性为集合时我们需要使用<collection>标签,property="studentList"表示在类中这个集合的名字,javaType="list"表示这个集合的类型,ofType="Student"表示集合中的泛型是什么类型,其余的配置方式和前面的都是一样的。
<resultMap id="findGrade" type="Grade">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<collection property="studentList" javaType="list" ofType="Student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
</collection>
</resultMap>
<select id="findStudent" resultMap="findGrade">
SELECT
g.id,
g.name,
s.id sid,
s.name sname
FROM
grade g
LEFT JOIN student s
ON g.id = s.gradeid
</select>
在这种一对多的关联查询中我们还可以使用嵌套查询来解决分页问题的产生,通过将一个复杂的查询转换为多个简单查询。例如查询所有年级以及每个年级对应的所有学生的信息。具体的思路是我们先通过一个简单查询将所有的年级信息查询出来,再根据年级的id来查询每个年级所对应的所有学生。
其中我们还是使用<resultMap>标签进行对映射关系的配置,和前面的关联查询不同<collection>标签的属性要比前面多两个column="id"表示我们接下来可能需要用到的属性,这里我们是需要根据年级的id进行之后的查询操作的,select="findStudent2"表示一个自定义的名字,和另一个简单查询的id值相同并且另一个简单查询的返回值类型也就变为了Student类型,还是同样将需要的学生信息的属性配置到<collection>标签中。
<resultMap id="GradeMap" type="Grade">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<collection property="studentList" javaType="list" ofType="Student" column="id" select="findStudent2">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
</collection>
</resultMap>
<select id="findStudent1" resultMap="GradeMap">
select * from grade
</select>
<select id="findStudent2" resultType="Student">
select id,name from student where gradeid = #{id}
</select>
动态sql
我们在进行查询操作时,有时查询的条件不止一个,这时我们就需要在select语句中手动添加查询条件例如:
select * from student where id = 1 and name = "小明"
前面我们将类中的属性创建为包装类类型,这样当参数无效时只有两中可能:一种是null,另一种是" ",这时我们如果不将为null或者为" "的属性删除的话我们就查询不到我们想要的数据,这时我们就需要动态地将查询条件进行改变,Mybatis框架中刚好有这种功能。我们可以将where语句的部分写入到Mybatis提供的<where>标签中,并将where后面的语句使用<if>标签进行判断。<where>标签会动态的进行插入或删除我们需要的或者不需要的sql语句,当<where>标签中的<if>条件成立时<where>标签就会将where加入到sql语句中,并且将条件成立的<if>标签中的语句也加入到sql语句中where的后面,而且<where>还会动态地判断where后面的第一个语句是否为and或者or,如果是还会将and和or进行删除,当<where> 标签中没有一个<if>条件成立时<where>标签就不会将where加入到sql语句中,从而实现动态sql的效果。
<select id="findStudent">
select * from student
<where>
<if test="id!=null&id!=''">
id = #{id}
</if>
<if test="name!=null&name!=''">
and name = #{name}
</if>
</where>
</select>
使用<trim>标签也可以达到这种效果,prefix="where"表示需要在语句中添加的前缀,只要有一个<if>标签成立就加前缀,反之则一个都不加。prefixOverrides="and|or"表示当插入语句中的第一个为and或者or时就将其删除。
<select id="findStudent">
select * from student
<trim prefix="where" prefixOverrides="and|or">
<if test="id!=null&id!=''">
id = #{id}
</if>
<if test="name!=null&name!=''">
and name = #{name}
</if>
<if test="gender!=null&gender!=''">
and gender = #{gender}
</if>
</trim>
</select>
<set>标签也是这样如果有<if>标签成立就插入set,如果没有就不插入。如果插入语句的最后一个为","则<set>标签就会将","进行删除。
<update id="updateStudent" parameterType="Student">
update student
<set>
<if test="name!=null&name!=''">
name = #{name},
</if>
<if test="gender!=null&gender!=''">
gender = #{gender}
</if>
</set>
where id = #{id}
</update>
<trim>标签prefix="set"表示插入的前缀,suffixOverrides=","表示当插入语句的最后一个为","就删除
<update id="updateStudent">
update student
<trim prefix="set" suffixOverrides=",">
<if test="name!=null&name!=''">
name = #{name},
</if>
<if test="gender!=null&gender!=''">
gender = #{gender}
</if>
where id = #{id}
</trim>
</update>
Mybatis的一级二级缓存
通过缓存可以减少用户对数据库访问的次数,进而减少了数据库的压力,提高查询性能。我们可以将通过相同的操作而得到相同的结果集的数据保存到缓存中,这样当用户进行多次相同的操作时就不会再向数据库中访问数据,而是直接通过缓存提高了查询效率。我们一般将一段时间内不会发生改变的数据存放在缓存中,例如对某些网页的访问,一个网页在一段时间内可能有很多的用户对其进行访问,我们不能让用户都去访问数据库中的数据,而是可以通过缓存拿到相同的数据,而缓存中的数据我们只需要让其每过一段时间自动刷新一次即可。
一级缓存
一级缓存的作用域是同一个SqlSession中,在一个SqlSession中如果执行多次相同的sql操作,那么从第二次操作开始,读取到的数据都是来自于缓存中的,当一个SqlSession不存在后,其对应的缓存也将被销毁,Mybatis默认开启的是一级缓存。
二级缓存
二级缓存是 SqlSessionFactory 级别的,作用域为同一个namespace中,当用户执行同一个namespace中的同一个sql语句时,第一次访问会先向数据库中访问并将访问后的数据存放在二级缓存中,当第二次执行同一个namespace中的同一个sql语句时,就会从缓存中读取数据,除非当缓存在超时,被声明需要刷新,或者sqlSession在执行update,insert,delete操作并commit提交时,会清空缓存区,防止读取的数据存在问题。
————————————————
版权声明:本文为CSDN博主「楠佩忆心轩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_52391639/article/details/125816937