在使用MyBatis进行数据库操作时,中间表关联查询是一种常见的需求,尤其是在处理多表关联关系时。以下是一个基于MyBatis实现中间表关联查询的详细步骤和示例,假设我们有一个典型的多对多关系:学生表(Student)
、课程表(Course)
和中间表学生课程表(StudentCourse)
。
1. 数据库表结构设计
学生表(Student)
sql复制
CREATE TABLE Student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT
);
课程表(Course)
sql复制
CREATE TABLE Course (
id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(50)
);
中间表(StudentCourse)
sql复制
CREATE TABLE StudentCourse (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES Student(id),
FOREIGN KEY (course_id) REFERENCES Course(id)
);
2. MyBatis配置
数据库连接配置(db.properties
)
properties复制
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/your_database_name?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=your_password
MyBatis配置文件(mybatis-config.xml
)
xml复制
<?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>
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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>
<mappers>
<mapper resource="StudentMapper.xml"/>
<mapper resource="CourseMapper.xml"/>
</mappers>
</configuration>
3. 实体类设计
Student类
java复制
public class Student {
private Integer id;
private String name;
private Integer age;
private List<Course> courses; // 学生所选课程
// Getters and Setters
}
Course类
java复制
public class Course {
private Integer id;
private String courseName;
private List<Student> students; // 课程所选学生
// Getters and Setters
}
4. Mapper接口和SQL映射文件
StudentMapper接口
java复制
import java.util.List;
public interface StudentMapper {
List<Student> getStudentsWithCourses();
}
StudentMapper.xml
xml复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.StudentMapper">
<resultMap id="StudentResultMap" type="Student">
<id property="id" column="student_id"/>
<result property="name" column="student_name"/>
<result property="age" column="student_age"/>
<collection property="courses" ofType="Course" column="course_id" select="getCourseById"/>
</resultMap>
<resultMap id="CourseResultMap" type="Course">
<id property="id" column="course_id"/>
<result property="courseName" column="course_name"/>
</resultMap>
<select id="getStudentsWithCourses" resultMap="StudentResultMap">
SELECT s.id AS student_id, s.name AS student_name, s.age AS student_age, sc.course_id
FROM Student s
LEFT JOIN StudentCourse sc ON s.id = sc.student_id
</select>
<select id="getCourseById" parameterType="int" resultMap="CourseResultMap">
SELECT id AS course_id, course_name
FROM Course
WHERE id = #{courseId}
</select>
</mapper>
5. 测试代码
测试类
java复制
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.io.Resources;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
public class MyBatisTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession session = sqlSessionFactory.openSession()) {
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
List<Student> students = studentMapper.getStudentsWithCourses();
for (Student student : students) {
System.out.println("Student: " + student.getName());
for (Course course : student.getCourses()) {
System.out.println(" Course: " + course.getCourseName());
}
}
}
}
}
6. 运行结果
假设数据库中有以下数据:
-
学生表:
-
ID: 1, Name: Alice, Age: 20
-
ID: 2, Name: Bob, Age: 22
-
-
课程表:
-
ID: 1, CourseName: Math
-
ID: 2, CourseName: English
-
-
中间表:
-
StudentID: 1, CourseID: 1
-
StudentID: 1, CourseID: 2
-
StudentID: 2, CourseID: 1
-
运行测试代码后,输出结果如下:
复制
Student: Alice
Course: Math
Course: English
Student: Bob
Course: Math
总结
通过MyBatis的resultMap
和嵌套查询,我们可以方便地实现中间表的关联查询。resultMap
用于定义结果映射规则,<collection>
标签用于处理多对多关系,通过select
属性指定子查询来加载关联对象。
怎么处理MySQL的慢查询?
1、开启慢查询日志,准确定位到哪个sql语句出现了问题
2、分析sql语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写
3、分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引
4、如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
优化语句:
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。
如何理解Springboot中的Starter?
starter就是定义⼀个starter的jar包,写⼀个@Configuration配置类、将这些bean定义在⾥⾯,然后在starter包的META-INF/spring.factories中写⼊该配置类,springboot会按照约定来加载该配置类开发⼈员只需要将相应的starter包依赖进应⽤,进⾏相应的属性配置(使⽤默认配置时,不需要配置),就可以直接进⾏代码开发,使⽤对应的功能了。
⽐如mybatis-spring-boot-starter,spring-boot-starter-redis,我们自己写一个starter自动配置启动,写一个我们自己的线程池,用@Configuration+@Bean定义一个线程池,将这个配置类的路径写到spring.factories中,就可以了,打上jar包,在其他地方引用,用@Autowired自动注入我们的Bean使用就可以。
如何实现一个IOC容器?
IOC(Inversion of Control),意思是控制反转,不是什么技术,而是一种设计思想,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
在传统的程序设计中,我们直接在对象内部通过new进行对象创建,是程序主动去创建依赖对象,而IOC是有专门的容器来进行对象的创建,即IOC容器来控制对象的创建。
在传统的应用程序中,我们是在对象中主动控制去直接获取依赖对象,这个是正转,反转是由容器来帮忙创建及注入依赖对象,在这个过程过程中,由容器帮我们查找级注入依赖对象,对象只是被动的接受依赖对象。
先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象
进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中
容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成对象的实例化工作
进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中
通过容器对象来获取对象,进行对象的获取和逻辑处理工作
提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁
SpringCloud核心组件有哪些?分别有什么作用?
1)服务发现——Netflix Eurek
该系统下还分为Eureka服务端和Eureka客户端,Eureka服务端用作服务注册中心,支持集群部署。Eureka客户端是一个java客户端,用来处理服务注册与发现。
2)客服端负载均衡——Netflix Ribbon
基于Http和Tcp的客户端负载均衡,使得面向REST请求时变换为客户端的负载服务调用,提供客户端的软件负载均衡算法。
3)断路器——Netflix Hystrix
它的作用是保护系统,控制故障范围。
4)服务网关——Netflix Zuul
提供api网关,路由,负载均衡等作用
5)分布式配置——Spring Cloud Config
提供服务端和客户端,服务器存储后端的默认实现使用git
8 讲一下对AOP的理解
AOP全称叫做 Aspect Oriented Programming 面向切面编程。它是为解耦而生的,解耦是程序员编码开发过程中一直追求的境界,AOP在业务类的隔离上,绝对是做到了解耦,在这里面有几个核心的概念:
切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schemabasedapproach)或者在普通类中以 @Aspect 注解(@AspectJ 注解方式)来实现。
连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”,“before” and “after”等等。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified 接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
9 讲一下对IOC的理解
IOC容器:实际上就是map,里面存的是各种对象(在XML里配置的bean节点,@respository,@service,@controller,@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里,扫描到打上上述注解的类还是通过反射的方式创建对象放到map里面。
这个时候map里面就有各种对象了,接下来我们再代码里面需要用到里面的对象时,再通过DI注入(@Autowired,@Resources,@Qualifier等注解),xml里bean 节点内的ref属性,项目启动时读取xml节点ref属性根据id注入,也会扫描到这些注解,根据类型或者id注入,id就是对象名。
控制反转:
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须去主动创建对象B或者使用已经创建的对象B,无论是创建还是使用对象B,控制权都在A类。
引入IOC容器后,对象A和对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建对象B注入到对象A所需要的地方。
通过前后对比,不难看出:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是控制反转。
依赖注入:
“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变成了由IOC容器自动注入。依赖注入是实现IOC的方法,就是由IOC在运行期间,动态的将某种依赖关系注入到对象之中。
10 索引的设计原则有哪些?
在进行索引设计的时候,应保证索引字段占用的空间越小越好,这只是一个大的方向,还有一些细节需要注意:
1、适合索引的列是出现在where子句中的列,或者链接子句中的列。
2、基数较小的表,索引效果差,没必要创建索引。
3、在选择索引列的时候,越短越好,可以指定某些列的一部分不,没必要用全部字段的值。
4、不要给表的没有给字段都创建索引,并不是索引越多越好。
5、定义有外键的数据列一定药创建索引。
6、更新频繁的字段不要有索引。
7、创建索引的列不要过多,可以创建组合索引,但是组合索引的列的个数不建议太多。
原文链接:https://blog.youkuaiyun.com/weixin_43660476/article/details/124371767