org.mybatis
mybatis
3.5.10
mysql
mysql-connector-java
5.1.38
junit
junit
4.13.2
test
org.projectLombok
lombok
1.18.26
com.github.pagehelper
pagehelper
4.1.4
org.mybatis
mybatis
3.5.10
mysql
mysql-connector-java
5.1.38
junit
junit
4.13.2
test
org.projectLombok
lombok
1.18.26
com.github.pagehelper
pagehelper
4.1.4
创建配置文件,添加数据库运行所需的配置信息,方便给mybatis配置文件赋值
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/hqyj03?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimeZone=Asia/Shanghai
password=yjg
username=root
public interface CourseMapper和public interface StudentMapper
CourseMapper.xml和StudentMapper.xml
创建在测试方法之前生成SqlSession对象
@Before
public void test1() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
//SqlSessionFactoryBuilder 是 MyBatis 提供的用于构建 SqlSessionFactory 的建造者类。
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory 是一个线程安全的工厂类,用于创建 SqlSession 实例
SqlSessionFactory build = builder.build(inputStream);
//SqlSession 是 MyBatis 提供的核心接口之一,它是用于与数据库进行交互的主要对象。
//简单来说,SqlSession 提供了执行 SQL 操作、提交事务、获取映射器(Mapper)等功能。
sqlSession = build.openSession(true);
}
CREATE TABLE `student` (
`stu_id` int(11) NOT NULL AUTO_INCREMENT,
`stu_name` varchar(255) DEFAULT NULL,
`stu_age` varchar(255) DEFAULT NULL,
`stu_salary` decimal(10,2) DEFAULT NULL,
`stu_birth` date DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`course_id` int(11) DEFAULT NULL,
PRIMARY KEY (`stu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
CREATE TABLE `course` (
`course_id` int(11) NOT NULL AUTO_INCREMENT,
`course_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`course_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
import lombok.Data;
import java.util.List;
@Data
public class Course {
private Integer courseId;
private String courseName;
private List students;//表示一个课程下有多个学生一对多
}
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.util.Date;
@Data
//@Alias("Stu")
public class Student {
private Integer stuId;
private String stuName;
private Integer stuAge;
private Double stuSalary;
private Date stuBirth;
private Date createTime;
private Integer courseId;
}
Mapper 接口
//通过id查询学生,传递单个参数
Student queryStudentById(Integer id);
映射文件语句
测试方法
@Test
public void test4(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.queryStudentById(2);
System.out.println(student);
}
使用#{名称}来接收接口方法中的参数值,使用占位符’?'来设置值。
Mapper 接口
Student queryStudentByNameAndAge(@Param("name") String name,@Param("age") int age);
映射文件语句
使用的是${}取值的方式,name值拼接了两个单引号
测试方法
@Test
public void test5(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.queryStudentByNameAndAge("kun", 18);
System.out.println(student);
}
也可以使用${名称}来接收,但是使用该方式是通过字符串拼接方式设值,它是直接插入
${}
用于表示直接替换参数的值,而 #{}
则用于进行参数的安全处理和预编译。在映射文件中使用 ${}
会将参数的值直接替换进 SQL 语句中,这样可能存在 SQL 注入的风险。因此,推荐使用 #{}
做为参数占位符。
接口中不设置别名的话
Student queryStudentByNameAndAge(String name,int age);
映射文件语句
运行测试方法就会报错
Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]
根据错误信息,在查询数据库时出现了参数找不到的错误。错误消息中提到可用的参数是 [arg1, arg0, param1, param2]
,而没有找到参数 name
。
原因
在获取方法上的参数时,会将参数值存入到一个map中,key名为arg1或者param1表示第一个参数,以此类推表示第二个,第三个。。。。因此会通过key名来获取参数值,map中没有name这个key。只能通过arg或者param的形式来获取。
语句的配置如下即可,也可以换成param1.。。。,当然也可以给参数设置别名。
Mapper 接口
//插入数据,参数为实体类型
int insertStu(Student stu);
映射文件语句
insert into student values (null,#{stuName},#{stuAge},#{stuSalary},#{stuBirth},now())
注:MyBatis会自动根据Mapper接口中定义的方法和映射文件中的SQL语句,获取实体类的属性并进行映射。
实体类的属性名称需要与映射文件中SQL语句中的占位符一致才能正确映射。同时,映射文件中的SQL语句也需要与Mapper接口中的方法名称一致,才能正确执行对应的SQL操作。
测试方法
@Test
public void test2(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student=new Student(null,"pp",45,555.55,new Date(),null);
int i = mapper.insertStu(student);
System.out.println(i);
}
Mapper 接口
//参数多种类型,指定数量查询
List queryStuByLimit(@Param("stu") Student student,@Param("count") Integer Count);
映射文件语句
测试方法
@Test
public void test6(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student s1=new Student();
s1.setStuAge(18);
List list = mapper.queryStuByLimit(s1, 2);
list.forEach(System.out::println);
}
在添加数据时,由于主键设置为自增的,会自动生成值,所以在插入新的数据时不需要设置id值。插入数据后如何获取到自动生成主键值
mapper
Mapper 接口
int insertStu(Student stu);
映射文件语句
insert into student values (null,#{stuName},#{stuAge},#{stuSalary},#{stuBirth},now())
测试方法
@Test
public void test(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student ss=new Student();
System.out.println(ss.getStuId());//null
ss.setStuName("kun");
ss.setStuAge(20);
ss.setStuSalary(4563.2);
ss.setStuBirth(new Date());
ss.setCourseId(2);
Integer sss = mapper.insertStu(ss);
System.out.println(ss.getStuId());//执行插入过后获取到了数据自增的id值
System.out.println(sss);
}
模糊查询语法:like %值%。因此在xml中编写sql时会进行字符串拼接。可能有时候会导致拼接不成功。因此建议使用concat函数进行%拼接。
CONCAT 函数
SQL 的 CONCAT 函数用于将两个或多个字符串连接在一起。它接受任意数量的参数,并按照参数的顺序将它们连接在一起。
使用 CONCAT 函数的一般语法如下:
CONCAT(string1, string2, ...)
其中,string1
、string2
表示要连接的字符串,可以是列名、常量值或表达式。
Mapper 接口
//模糊查询
List queryStuByNameLike(String name);
映射文件语句
测试方法
@Test
public void test7(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List nameLike = mapper.queryStuByNameLike("k");
nameLike.forEach(System.out::println);
}
为什么要使用自定义结果映射集?
当使用实体类来做为返回值类型的时候,会将数据库字段和实体类中的属性进行一一映射。
一张表对应一个实体类,如果此时进行多表查询,那么查询到的记录是包含多张表的记录,而
将一个实体类做为返回值接收是无法获取到多表查询到的所有值的。因此需要手动定义结果
集,来接收来自数据库中多表查询到的值。
多表查询涉及到以下关系:一对一关系,一对多关系,多对多关系
使用
元素:通过定义
元素,你可以指定如何映射查询结果到自定义的对象。在
中,你可以使用
、
、
、
等元素来描述对象的属性和关联关系。
元素用于指定主键属性的映射。你可以使用它来指定哪个属性对应数据库表的主键列。
元素用于描述普通属性的映射。通过
元素,你可以将查询结果中的某一列或表达式的结果映射到自定义对象的一个属性上。
元素用于描述对象之间的一对一关联关系。如果你的自定义对象中包含了其他对象作为属性,你可以使用
元素来定义该关联关系。
元素用于描述对象之间的一对多关联关系。如果你的自定义对象中包含了一个集合类型的属性,表示与其他对象之间存在一对多的关系,你可以使用
元素来定义该关联关系。
column
表示数据库表中的列名。通过在
或
元素中指定 column
属性,你可以告诉 MyBatis 将查询结果中的某个列映射到对象的相应属性上。例如,假设数据库表中有一个列名为 stu_name
,你可以通过 column="stu_name"
将其映射到自定义对象的某个属性上。
type
表示 Java 类型或 JDBC 类型。在
或
元素中,你可以使用 type
属性来指定属性的类型或 JDBC 类型。这对于 MyBatis 在处理类型转换时非常有用。例如,假设你的查询结果中的某列是字符串类型,但你想将其映射到自定义对象的 Integer 类型属性上,你可以通过 type="java.lang.Integer"
来指定属性的类型。
property
表示对象中的属性名。在
或
元素中,你可以使用 property
属性来指定查询结果映射到自定义对象的哪个属性上。例如,假设你的自定义对象有一个属性名为 name
,你可以通过 property="name"
来指定将查询结果映射到该属性上。
javaType
是用来指定属性的 Java 类型的属性。javaType
属性用于告诉 MyBatis 自定义对象中的某个属性的类型是什么。它通常在
或
元素中使用。
两个实体类,一个Student,一个Course,两个的属性都写好,当使用数据库查询给对象属性赋值时,如果没有那么对象的属性是null
@Data
public class Student {
private Integer stuId;
private String stuName;
private Integer stuAge;
private Double stuSalary;
private Date stuBirth;
private Date createTime;
private Integer courseId;
private Course course;///表示一个学生只有一门课程,多个就要用LIst
public Student() {}
public Student(Integer stuId, String stuName, Integer stuAge) {
this.stuId = stuId;
this.stuName = stuName;
this.stuAge = stuAge;
}
}
@Data
public class Course {
private Integer courseId;
private String courseName;
private List students;//表示一个课程下有多个学生一对多
}
Mapper 接口
//查询学生以及课程信息
List queryStuAllInfo();
映射文件语句
第一种:使用级联关系
这段代码定义了一个名为 stuInfoMap1
的
,用于将查询结果映射到 student
对象及其关联的 Course
对象的属性上。其中包含了主键属性、非主键属性以及关联对象的映射关系。
Student
类中的 setCourse
方法(因为引入了lombok依赖)会被 MyBatis 自动调用,传入一个 Course
对象,该对象包含从数据库查询结果中映射得到的 courseId
和 courseName
值。然后,setCourse
方法将这些值设置到 Student
对象的关联属性中
第二种:使用association标签自定义引用数据类型映射关系
元素特点:
元素用于指定主键属性的映射,但同样也可以用于映射普通属性。实际上,
元素是
元素的一种特殊形式。
与stuInfoMap1
一样也是使用了Student
类中的 setCourse
方法,设置值。
第三种:分步查询,查询到学生的信息后,再根据学生的课程id,查询课程信息
分析
元素的 select
属性用于指定一个查询语句的唯一标识符,该语句将在查询主实体时同时加载关联实体。这个查询语句可以是一个独立的 SQL 语句或者引用其它已定义的 元素。
使用
元素设置了 property="course"
,表示关联属性的名称为 course
。通过 select
属性指定了查询关联对象 Course
的 SQL 语句的命名空间和方法,这里是 com.yjg.mybatis.mapper.CourseMapper.queryCourseById
。通过 column
属性指定了与 student
对象关联的列名 course_id
。MyBatis 可以根据 course_id
的值,去查询并获取对应的课程信息,并将其映射到 Course
对象的属性上。然后赋值给主实体对象 Student
的关联属性 course
。
映射文件语句
分析
标签表示这是一个查询语句。id="queryStuAllInfo"
表示这个查询语句的唯一标识符是 “queryStuAllInfo”,可以通过这个标识符在代码中进行调用和引用。resultMap="stuInfoMap2"
表示查询结果将使用名为 “stuInfoMap2” 的 ResultMap 进行映射。ResultMap 是一个 MyBatis 的配置,用于将数据库查询结果映射到 Java 对象上。测试方法
@Test
public void test9(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//CourseMapper courseMapper = sqlSession.getMapper(CourseMapper.class);
List list = mapper.queryStuAllInfo();
list.forEach(System.out::println);
}
注: StudentMapper
对应的映射文件中采用stuInfoMap3自定义映射结果集时,有一部分内容涉及到了关联属性 course
的查询。是通过调用 CourseMapper
中的方法来查询关联的 Course
对象。所以需要CourseMapper
接口的实例对象。
第四种:通过set方法暴力传入
以下是暴力传入
映射文件语句
StudentMapper.xml中的语句
CourseMapper.xml中的语句
测试方法
@Test
public void test9(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
CourseMapper courseMapper = sqlSession.getMapper(CourseMapper.class);
List list = mapper.queryStudents();
//这时候Course对象是null 下面是暴力传入Course对象
for (int i = 0; i < list.size(); i++) {
//获取课程id
Integer courseId = list.get(i).getCourseId();
//使用courseId查询course信息
Course course = courseMapper.queryCourseById(courseId);
//将查询到的course设置到每个student对象
list.get(i).setCourse(course);
}
list.forEach(System.out::println);
}
查询每门课程下的有哪些学生
Mapper 接口
//查询课程以及课程包含的学生
List queryCourse();
自定义结果映射集
collection属性介绍
在 MyBatis 中,“collection” 属性通常用于与 ResultMap 关联使用:
第一种:使用collction标签来处理集合映射
第二种:使用分步查询
StudentMapper.xml表中语句配置
CourseMapper.xml中语句配置
映射文件语句
测试方法
@Test
public void test10(){
CourseMapper courseMapper = sqlSession.getMapper(CourseMapper.class);
List list = courseMapper.queryCourse();
list.forEach(System.out::println);
}
当自定义对象具有带参数的构造函数或工厂方法时,MyBatis 允许在 SQL 查询语句中直接使用它们来创建对象。这使得你可以通过 SQL 查询结果将数据映射到自定义对象的属性上。
假设有一个自定义对象 Student
,它有一个带参数的构造函数:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 其他属性和方法
// ...
}
现在,可以在 MyBatis 的查询语句中使用这个构造函数来创建 Student
对象并将查询结果映射到它的属性上。以下是一个示例:
在上面的示例中,将查询结果映射到 Student
类型的对象上。MyBatis 会查找 Student
类中的带有相应参数的构造函数,并使用该构造函数创建对象实例。然后,将查询结果中的列值分别赋值给构造函数参数所对应的属性。
同样地,如果自定义对象有一个带参数的工厂方法,你也可以在 SQL 查询语句中使用工厂方法来创建对象。以下是一个示例:
public class Student {
private String name;
private int age;
public static Student create(String name, int age) {
return new Student(name, age);
}
// 其他属性和方法
// ...
}
在上面的示例中,MyBatis 会查找 Student
类中的名为 create
的静态工厂方法,并使用该方法创建对象实例。然后,将查询结果中的列值作为参数传递给工厂方法,并将返回的对象实例作为最终的映射结果。
通过使用带参数的构造函数或工厂方法,可以自定义对象的创建过程,并且能够更灵活地与数据库中的数据进行映射。
动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中,开发人员通常
需要手动拼接SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。动态 SQL 大
大减少了编写代码的工作量,更体现了 MyBatis 的灵活性、高度可配置性和可维护性。
MyBatis 也可以在注解中配置 SQL,但是由于注解功能受限,且对于复杂的 SQL 语句来说可
读性差,所以使用较少。
通过条件查询学生信息时,可以通过学生的用户名进行查询,也可以通过年龄进行查询(where age=?),也可以同时使用名字和年龄进行查询(where name=? and age=?)。按照以上的条件,需要写三个不同的SQL操作。实质上,三个SQL的区别在于条件查询的字段不同,因此可以使用动态SQL来动态生成不同查询条件的SQL语句。
SQL语句
Mapper 接口
//根据学生属性查询学生
List queryStudentByParams(Student student);
映射文件语句
用法
:是 MyBatis 中用于构建动态条件的元素,在这里作为条件容器。
:如果传入的参数 stuName 和 stuId 均不为 null,则生成对应的条件语句,并且使用 AND 连接符拼接条件。
:如果传入的参数 stuId 不为 null,则生成对应的条件语句,并且使用 AND 连接符拼接条件。
:如果传入的参数 stuName 不为 null,则生成对应的条件语句,并且使用 AND 连接符拼接条件。
测试方法
@Test
public void test11(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu=new Student();
stu.setStuName("kun");
stu.setStuId(2);
List list = mapper.queryStudentByParams(stu);
list.forEach(System.out::println);
}
choose 标签按顺序判断其内部 when 标签中的判断条件是否成立,如果有一个成立,则执行相应的 SQL 语句,choose 执行结束;
如果都不成立,执行 otherwise 中的 SQL 语句。这类似于 Java 的switch 语句,choose为switch,when 为case,otherwise则为default。
SQL语句1
SQL语句2
SQL语句3
SQL语句4
Mapper 接口
//根据学生属性查询学生2
List queryStudentByParams2(Student student);
映射文件语句
测试方法
@Test
public void test12(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu=new Student();
stu.setStuName("kun");
List list = mapper.queryStudentByParams2(stu);
list.forEach(System.out::println);
}
如果不设置参数值,打印结果是SQL中执行拼接的是otherwise中的SQL语句
select * from student where 1=1
and stu_name=#{stuName} and stu_id=#{stuId}
如果把1=1这个条件去除后,则会拼接成一个错误格式的SQL,SQL中的语句缺少where关键字,如果不写1=1,就还要考虑and的位置。
所以有,where标签在不用写where 1=1这个条件的情况下,拼接条件查询语句时,自动添加where关键字,保证sql格式正确。
元素可以用于将多个条件语句组合成一个完整的 WHERE 子句。它提供了一种简化 SQL 编写的方式,并且可以动态地根据条件判断生成有效的 WHERE 子句。
标签会自动处理条件语句之间的逻辑关系,它会移除开头的 AND 或 OR,以免在没有条件的情况下生成无效的语句。
MyBatis 的
标签是一种用于动态生成更新语句中 SET 子句的元素。
在使用 MyBatis 更新记录时,经常需要根据传入的参数来决定更新哪些字段、更新的值是什么。
标签就提供了这样的功能,可以根据条件动态地生成 SET 子句。
字段=更新值
字段=更新值
字段=更新值
Mapper 接口
Integer UpdateStudent(Student student);
映射文件语句
update student
stu_name=#{stuName},
stu_age=#{stuAge},
where stu_id=#{stuId}
测试方法
@Test
public void test20(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu=new Student();
stu.setStuId(2);
stu.setStuAge(666);
stu.setStuName("kun");
System.out.println(mapper.UpdateStudent(stu));
}
相当于for循环语句,能够在SQL中循环遍历出参数为List或者Set等
集合中的参数值
使用场景:一次性添加/删除多个记录
参数值
collection
属性有以下三个取值collection
属性有以下三个取值
list
:表示接受一个 List 类型的参数。array
:表示接受一个数组类型的参数。map key
:表示接受一个 Map 类型的参数,并以 Map 的键作为集合元素的属性。属性说明
item:表示本次迭代获取到的每个元素,若collection为List、
Set或者数组,则表示其中存储的每个元素;若为map,则代表
key-value的value,该参数为必选。
index:表示当前迭代的元素的下标,在map中,则指代的是key
值
open:表示循环从哪里开始
separator:表示在每次遍历出来的值之间以什么符号作为分隔
符。
close:表示该语以什么结束。
collection:表示迭代集合的名称,或者使用@Param指定的名称
Mapper 接口
//批量插入:一次性插入多条数据
Integer insertStudentBatch(@Param("stuList") List stuList);
映射文件语句
insert into student(stu_id,stu_name,stu_age) values
(null ,#{one.stuName},#{one.stuAge})
测试方法
@Test
public void test13(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
ArrayList stuList=new ArrayList<>();
stuList.add(new Student(null, "ee", 66));
stuList.add(new Student(null, "tt", 99));
stuList.add(new Student(null, "yy", 88));
Integer integer = mapper.insertStudentBatch(stuList);
System.out.println(integer);
}
其他属性值没有输入为什么还可以执行?
如果你在插入语句中没有为某个属性指定具体的属性值,且该属性在数据库表中被定义为可为NULL(nullable),那么默认情况下会将该属性值设置为NULL。
Mapper 接口
//批量删除
Integer deleteStudentBatch(@Param("deleteList") List deleteList);
映射文件语句
delete from student where stu_id in
#{i}
测试方法
@Test
public void test14(){
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
ArrayList list=new ArrayList<>();
list.add(16);
list.add(17);
list.add(18);
Integer integer = mapper.deleteStudentBatch(list);
System.out.println(integer);
}
执行结果
分页插件的使用----pageHelper
导入pageHelper依赖
com.github.pagehelper
pagehelper
4.1.4
在核心配置文件中配置分页插件
代码
Mapper 接口
ListqueryUserByPage();
映射文件语句
测试方法
public void test06(){
PageHelper.startPage(1,2);//表示第一页 每页显示两条数据
List users = mapper.queryUserByPage();
PageInfo pageInfo = new PageInfo<>(users);
System.out.println("当前页码:"+pageInfo.getPageNum());
System.out.println("每页显示条数:"+pageInfo.getPageSize());
System.out.println("当前页实际显示条数:"+pageInfo.getSize());
System.out.println("总记录数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("上一页的页码:"+pageInfo.getPrePage());
System.out.println("下一页的页码:"+pageInfo.getNextPage());
System.out.println("是否为第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否为最后一页:"+pageInfo.isIsLastPage());
System.out.println("是否存在上一
页:"+pageInfo.isHasPreviousPage());
System.out.println("是否存在下一页:"+pageInfo.isHasNextPage());
}