2023-08-23 八股

八股理解+背诵:技术栈:Java后端开发

Java基础

面向对象三大特性

Java是一种面向对象的编程语言,它具有三大主要的面向对象特性,分别是封装、继承和多态。

  1. 封装(Encapsulation):

比喻:在农场游戏中对动物的管理。你并不让其他人直接进入你的农场,而是提供了一个方法,比如:“喂养动物”

封装是指将属性和方法捆绑在一起,形成一个类。它限制了外部对类内部数据的直接访问,而是通过类提供的方法来进行操作。这样可以隐藏内部实现细节,提供了更好的安全性和控制,使得代码维护更容易。通过封装,可以实现信息隐藏,防止不合法的修改和访问。

  1. 继承(Inheritance):

普通鸡和特别鸡都继承自一个通用的“鸡”类,他们都有一个共同的“下蛋”方法。

继承允许一个类(子类)基于另一个类(父类)来构建,从而继承父类的属性和方法。子类可以扩展父类的功能,同时还可以覆盖父类的方法以适应自己的需求。继承有助于代码重用、层次化设计和扩展性,它建立了类之间的层次关系。

  1. 多态(Polymorphism)

集合里面容纳各种动物:鸡、猪、牛等。当你调用集合中的每个动物的“发出声音”方法时,不同类型的动物会发出不同的声音。

多态允许不同的类实现相同的方法名称,但具体的实现可以不同。这使得你可以用一个通用的接口来处理多个不同的对象。多态性可以通过继承和接口实现。在Java中,多态性使得你可以根据实际对象的类型来动态调用方法,提供了更灵活的代码结构和更好的代码扩展性。

反射

反射机制的概念
反射是一种高级特性,允许在运行时动态地检查类、获取类的信息,以及创建类的对象。

反射的应用
Java中的对象有两种类型:编译时类型和运行时类型
编译时类型指在生命对象时所采用的类型;运行时类型指为对象赋值时所采用的类型。
在如下代码中,person对象的编译时类型为Person,运行时类型为Student,因此无法在编译时获取在Student类中定义的方法:

Person person = new Student();

因此,程序在编译期间无法预知该对象和类的真实信息,只能通过运行时信息来发现该对象和类的真实信息,而其真实信息(对象的属性和方法)通常通过反射机制来获取。

Java的反射API

Java的反射API主要用于在运行过程中动态生成类、接口或对象等信息。常用API如下:

  • Class类:用于获取类的属性、方法等信息。
  • Field类:表示类的成员变量,用于获取和设置类中的属性值。
  • Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法。
  • Constructor类: 表示类的构造方法。

Method的invoke方法
Method类的invoke方法用于在运行时调用特定对象的方法。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Person{
    private String name;

    public Person(String name){
        this.name = name;
    }

    public void introduce(){
        System.out.println("Hello,my name is " + name);
    }

}
public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        //获取类对象
        Class<?> personClass = Class.forName("JavaReflection.Person");

        //创建实例
        Constructor<?> constructor = personClass.getConstructor(String.class);
        Object person = constructor.newInstance("Alice");

        //获取方法对象
        Method introducedMethod = personClass.getDeclaredMethod("introduce");
        introducedMethod.invoke(person);

        //访问属性
        Field nameField = personClass.getDeclaredField("name");
        nameField.setAccessible(true);
        System.out.println("Name: " + nameField.get(person));
    }
}

首先使用构造函数对象的 newInstance 方法创建了一个 Person 类的实例,传递了名字 "Alice"。然后,使用 invoke 方法,将上一步得到的 Method 对象应用到之前创建的 Person 实例上。这样,introduce 方法就被调用,打印了 "Hello, my name is Alice"。

反射的步骤
(1)获取想要操作的类Class对象,该Class对象是反射的核心,通过它可以调用类的任意方法。
(2)调用Class对象所对应的类中定义的方法,这是反射的使用阶段。
(3)使用反射API来获取并调用类的属性和方法等信息。

获取Class对象的3种方法

(1) 调用某个对象的getClass()方法以获取该类对应的Class对象

Class<?> myClass = myObject.getclass();

(2) 调用某个类的class属性以获取该类对应的Class对象:

Class<?> myClass = MyClass.class;

(3)调用Class类中的forName静态方法以获取该类对应的Class对象,这是最安全、性能也最好的方法:

Class<?> myClass = class.forName("com.example.MyClass"); 

(4)使用类加载器(ClassLoader):类加载器用于加载类的字节码,并生成对应的Class对象。可以使用系统类加载器(ClassLoader.getSystemClassLoader())或自定义的类加载器。

classLoader classLoader = ClassLoader.getSystemClassLoader();
class<?> myClass = classLoader.loadClass("com.example.MyClass");

注意,获取Class对象时可能会抛出ClassNotFoundException异常,特别是在使用Class.forName()时,如果指定的类不存在。

创建对象的两种方式

使用构造函数和使用反射。

  1. 使用构造函数:每个类都有一个或多个构造函数,用于初始化对象的属性和状态。
public class Person {
    private String name;

    // 无参数构造函数
    public Person() {
        name = "Unknown";
    }

    // 带参数的构造函数
    public Person(String n) {
        name = n;
    }

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }

    public static void main(String[] args) {
        // 使用不同的构造函数创建对象
        Person person1 = new Person();
        person1.sayHello(); // 输出:Hello, my name is Unknown

        Person person2 = new Person("Alice");
        person2.sayHello(); // 输出:Hello, my name is Alice
    }
}

在这个例子中,Person 类有两个构造函数,一个是无参数构造函数,另一个是带参数的构造函数。在 main 方法中,我们分别使用这两个构造函数来创建对象,并调用 sayHello 方法来输出结果。这些构造函数代码会在使用 new 关键字创建对象时被调用。

  1. 使用反射:通过反射,你可以在不知道类名的情况下实例化对象。

(1) 使用Class对象的newInstance方法创建实例(要求默认的空构造函数)

Class<?> myClass = MyClass.class;
Object instance = myClass.newInstance();

(2) 使用Constructor对象的newInstance方法创建实例:

try {
    Class<?> myClass = Class.forName("com.example.MyClass"); // 替换为实际类的完整路径
    Constructor<?> constructor = myClass.getDeclaredConstructor(); // 获取默认构造函数
    constructor.setAccessible(true); // 如果构造函数是私有的,需要设置可访问
    Object instance = constructor.newInstance(); // 创建对象
    // 现在你可以使用 instance 来操作对象
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
    e.printStackTrace();
}

在这个示例中,我们使用了 Class.forName() 来获取类的 Class 对象,然后通过 getDeclaredConstructor() 获取构造函数,最后使用 newInstance() 创建对象。

反射机制的应用有哪些?

  1. 动态实例化对象和扩展功能:反射允许你在运行时通过类的全路径名来实例化对象,这对于一些需要在编译时无法确定具体类名的情况非常有用。例如,你可以通过用户提供的类名来加载插件或模块,并在程序中使用这些动态加载的类。这种能力有助于实现灵活的架构和插件系统,从而使应用程序更具可扩展性。
  2. 获取类的成员信息:反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  3. 访问私有成员进行测试覆盖率:利用反射API访问私有字段、方法等,并对其进行测试,以保证测试代码覆盖率。

例子

(1) JDBC的数据库连接: 在Java中,使用JDBC(Java Database Connectivity)连接数据库时,需要加载数据库驱动并创建连接。通常,数据库驱动程序是以JAR文件的形式提供的,而在连接数据库之前,需要使用Class.forName()方法动态加载驱动类,这就是一个典型的反射应用。通过反射加载驱动类,可以将数据库驱动的实现类加载到内存中,从而使程序能够与数据库进行交互。

(2)Spring框架的使用: Spring框架是一个广泛使用的轻量级Java企业级应用开发框架。Spring使用了大量的反射来实现依赖注入(Dependency Injection)和面向切面编程(Aspect-Oriented Programming)。通过反射,Spring可以在运行时检查类的注解、方法和字段,并动态地将依赖关系注入到对象中,以及在方法调用前后添加额外的逻辑。Spring的反射应用帮助开发人员实现了松耦合、易于测试和可维护的应用程序。

在Spring 框架中使用经典的 XML 配置模式来装载 Bean 的过程:(魔术师表演魔术)

  1. 加载配置文件(魔术道具): 在 Spring 中,你可以将应用程序的配置信息以 XML 或 properties 文件的形式存储。这些文件包含了关于 Bean 定义、依赖关系等的信息。第一步是将这些配置文件加载到内存中,这样 Spring 框架就可以对它们进行解析。
  2. 解析配置信息(选择道具) :在配置文件中,你会定义各种 Bean,以及它们的属性、依赖关系等。Spring 框架会解析这些配置信息,了解哪些类应该被实例化、哪些属性需要注入等。
  3. 使用反射获得 Class 实例(道具变魔术): 在第二步中,Spring 框架会得到类名的字符串,以及与之相关的属性信息。接下来,它会使用反射机制根据这个类名获取相应的 Class 实例,这个实例表示了该类的元数据和行为。
  4. 动态配置实例属性(变魔术的过程): 在得到了 Class 实例后,Spring 框架会使用反射来实例化对象,并将配置的属性值动态注入到实例中。这意味着你不必手动创建对象和设置属性,Spring 会帮你自动完成这些工作。
    将 Spring 框架的配置和反射过程比作魔术师的表演,有助于理解如何通过配置文件、解析、反射和实例化来实现动态的组件创建和管理。

Spring 使用这种方式的好处

  • 通过配置文件装载 Bean,你可以将实例的创建和属性设置从代码中分离出来,使代码更加模块化和易于维护。
  • 配置文件提供了一种中心化的管理方式,使你可以在不改变代码的情况下调整应用程序的行为。
  • 使用反射可以让 Spring 在运行时根据配置文件的内容动态地加载类和设置属性,从而实现高度的灵活性和可扩展性。

泛型

泛型是JDK1.5的一个新特性,是一种参数化类型的概念。它允许你在编写类、接口和方法时将类型作为参数传递,并在编译时确定具体的类型。

泛型的好处

1. 类型安全:通过在编译时进行类型检查,泛型确保你只能插入和获取正确类型的数据,可以避免在运行时遇到ClassCastException异常,从而提高代码的健壮性和可维护性。

2. 消除强制类型转换: 使用泛型,你可以在声明和创建集合、类或方法时指定其预期类型。这消除了许多需要手动进行的强制类型转换。

3. 潜在的性能收益: 当使用泛型时,编译器会在编译时确保类型的正确性,然后在生成字节码时会将泛型类型信息擦除,因此在运行时几乎不会引入额外的开销。这使得泛型在性能方面与手动强制类型转换相比可能更有效率。

类型擦除

当你声明一个泛型类型并在代码中使用它时,编译器会在编译阶段执行类型检查,但是在生成最终的字节码文件(.class 文件)时,所有的泛型信息都被移除了。
Java类型擦除的具体步骤
首先,查找用来替换类型参数的具体类(该具体类一般为Object),如果指定了类型参数的上界,则以该上界作为替换时的具体类;然后,把代码中的类型参数都替换为具体的类。

泛型标记和泛型限定

泛型标记E,T,K,V,N,?

限定通配符:

  1. <? extends T>:它通过确保类型必须是T的子类(包括T本身)来设定类型的上界(Upper Bounded Wildcard)。例如,List<? extends Number> 表示这个集合可以持有任何是 Number 类型的子类。

  2. <? super T>:它通过确保类型必须是T的父类(包括T本身)来设定类型的下界(super wildcard)。例如,List<? super Integer> 表示这个集合可以持有任何是 Integer 类型的父类。

非限定通配符:

<?>:这是一种非限定通配符,也被称为无界通配符。它可以用任意类型来替代,表示这个泛型类型可以持有任何类型。例如,List<?> 表示这个集合可以持有任何类型的数据,包括 List<A>List<B>List<C> 等等。

MySQL

事务

并发控制问题(在共享的画板上绘画)

  1. 脏读 (Dirty read): 这指的是一个事务读取了另一个事务尚未提交的数据。当事务 B 对数据进行修改,但由于某种原因回滚了,事务 A 已经读取了这个被修改但未提交的数据,导致事务 A 看到了"脏数据"。

想象有人在画画,但还没有画完,却被另一个人提前看到了。如果第一个人决定不画了,第二个人看到的就是一张未完成的画,这就是脏读。

  1. 不可重复读 (Unrepeatable read): 这指的是一个事务在多次读取同一数据的过程中,发现数据的值发生了变化。这可能是因为在事务进行读取操作期间,另一个事务对数据进行了修改并提交,导致第一个事务在不同时间点读取到了不同的数据。

如果一个人在观看一幅画的过程中,另一个人改变了这幅画,那么第一个人再次观看时会发现画变了。

  1. 幻读 (Phantom read): 幻读类似于不可重复读,但更侧重于新增或删除操作。这发生在一个事务读取了一些数据后,另一个并发事务在这之间插入了一些新的数据,导致第一个事务在随后的查询中发现出现了一些之前不存在的记录,就好像"幻般"地多出了一些数据。

想象一个人在画一些东西,然后另一个人在这之间增加了一些新的图案。当第一个人再次看画时,会发现画上多了一些之前没有的图案,就好像出现了幻觉一样。

不可重复读侧重于修改,幻读侧重于新增或删除(多了或少了行),脏读是一个事务回滚影响另外一个事务。
因为多个事务在并发执行时可能会相互影响,导致数据的一致性受损。数据库管理系统需要采取一些并发控制机制来解决这些问题,确保数据的正确性和一致性。常见的并发控制机制包括锁机制、事务隔离级别等。

事务的四个特征

数据库管理系统(DBMS)中事务的四个关键特征,这些特征确保数据库在并发处理和故障恢复的情况下能够保持数据的一致性和可靠性。

  1. 原子性 (Atomicity):原子性指的是事务是不可分割的最小执行单位,它要么完全成功,要么完全失败,没有中间状态。

  2. 一致性 (Consistency):一致性是指在事务执行前后,数据库的数据保持一致。例如,如果一个转账事务执行失败,那么转账者和收款人的总金额不应该发生变化,以保持数据的一致性。

  3. 隔离性 (Isolation):隔离性确保在多个事务并发执行的情况下,每个事务都被视为在独立的执行环境中运行,不会相互干扰。这是为了避免并发执行可能引发的数据不一致问题。各个事务的操作在执行过程中应该互不干扰,不会看到其他事务未提交的修改,从而保持数据库的完整性。

四种隔离级别:

  1. READ-UNCOMMITTED(读取未提交): 在这个隔离级别下,一个事务可以读取其他事务尚未提交的数据变更。这可能会导致脏读(读取到未提交的数据)、幻读(读取到其他事务插入的数据)以及不可重复读(读取到其他事务已提交的数据的不同版本)。
  2. READ-COMMITTED(读取已提交): 这是Oracle的默认隔离级别。它允许事务读取其他已提交事务的数据。
  3. REPEATABLE-READ(可重复读): 这是MySQL的默认隔离级别。它确保在同一事务内对同一行数据的多次读取结果保持一致,除非数据是由该事务自身所修改。
  4. SERIALIZABLE(可串行化): 这是最严格的隔离级别,它确保所有事务依次顺序执行,就像是串行执行一样。但会对性能产生较大影响,因为事务需要一个接一个地执行。
-- 设置隔离级别为 REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
隔离级别
  1. 持久性 (Durability):持久性确保一旦事务被提交,其所做的修改将会永久保存在数据库中,即使在数据库发生故障的情况下也不会丢失。

总之,这四个特征共同确保了数据库管理系统在处理事务时能够保持数据的一致性、可靠性和隔离性,从而使数据库在各种情况下都能够正确地处理并发操作和故障恢复。

MySQL的隔离级别是如何实现的?

MySQL的隔离级别是通过锁和多版本并发控制(MVCC,Multiversion Concurrency Control)等技术实现的。不同的隔离级别会使用不同的方法来处理并发读写操作,以确保数据的一致性和隔离性。以下是MySQL中各个隔离级别的实现方式:

  1. 读未提交(Read Uncommitted): 这是最低级别的隔离。在这个级别下,读操作不会加锁,也不会阻止其他事务修改数据。因此,可能会导致脏读问题(读取到未提交的数据)。MySQL中实现这个隔离级别时,读操作和写操作都不会加锁,但读操作会看到其他事务未提交的数据

  2. 读已提交(Read Committed): 在这个级别下,写操作会加排他锁,防止其他事务同时修改数据。读操作不会加锁,但会通过MVCC机制避免脏读。MySQL会通过生成"read view"来表示当前事务可以看到的数据版本,从而避免读到未提交的数据。

  3. 可重复读(Repeatable Read): 在这个级别下,写操作会加排他锁,读操作会通过MVCC避免脏读和不可重复读。MySQL会在事务开始时创建一个read view,该视图表示事务开始时已存在的数据版本。事务内的所有读操作都将使用这个视图,从而保证在事务期间看到一致的数据版本。

  4. 串行化(Serializable): 这是最高级别的隔离。在这个级别下,写操作会加排他锁,并阻止其他事务的读写操作。读操作会通过MVCC机制避免脏读、不可重复读和幻读。事务将按照顺序逐个执行,以避免并发冲突。

对于MVCC机制,MySQL使用了多个技术来实现

  • 版本链(Version Chain): MVCC通过创建数据行的多个版本来实现。每当进行写操作(插入、更新、删除)时,数据库会为该数据行创建一个新的版本,并保留之前版本的信息。这就构成了版本链,每个版本都有一个时间戳标识它的创建时间。版本链的存在允许不同的事务在同一时间线上操作数据,避免了脏读和不可重复读

  • Read View(读视图):RC和RR隔离级别下,事务会生成一个读视图(也叫读快照,Snapshot Read),指示事务开始时可以看到的数据版本。这个视图基于事务开始时的时间戳或事务ID,作为该事务的数据视角。事务的读操作会根据这个读视图来选择合适的数据版本,从而避免了脏读和不可重复读

快照读保证事务只能看到在事务开始时已经存在的数据

  • Undo Log(撤销日志): 在MVCC中,为了实现回滚操作和数据版本的管理,数据库使用了Undo Log。每当事务对数据行进行修改时,会记录一条Undo Log,其中包含了该数据行之前的版本信息。这样,在事务回滚或其他操作时,可以使用Undo Log将数据还原到之前的状态。这也有助于避免脏读和不可重复读

  • 间隙锁(Gap Locks): 为了避免幻读,MVCC在某些隔离级别下使用了间隙锁。间隙锁会锁定一个范围,而不仅仅是某个数据行。当一个事务尝试在这个范围内插入新数据时,间隙锁会阻止其他事务插入类似的数据,从而确保了事务的一致性。

间隙锁则防止其他事务在事务处理过程中插入新的数据,从而确保数据的一致性。

综合来说,MySQL的隔离级别就是在多个事务同时访问数据库时,如何控制它们看到数据的方式。不同的隔离级别使用了不同的策略,例如锁和时间戳,以确保并发操作时数据的正确性和一致性。

Redis

缓存雪崩

缓存雪崩是指当缓存中的大部分数据在同一时间段内过期或失效时,导致大量的请求直接访问数据库,从而造成数据库负载剧增,甚至引发数据库崩溃的现象。

现在,想象一下你有一天突然发现所有的书架上的书都变得无法读取了,这时你只能去图书馆找书。如果很多人都在同一时间遇到相同的问题,图书馆就会变得非常拥挤,可能会有太多人挤在一起,导致混乱甚至无法正常工作。

为了避免Redis缓存雪崩,可以考虑以下策略

  1. 合理的缓存失效时间: 设置缓存数据的失效时间时,尽量随机分布失效时间,避免在同一时间点大量缓存数据失效。

将不同的产品的缓存过期时间稍微错开,避免大量缓存同时失效。

  1. 热点数据预加载: 针对一些热点数据,在失效前提前进行刷新,保证缓存不会在同一时间段内失效。

提前加载一些热门产品的缓存,确保即使缓存失效,至少还有一部分数据可用。

  1. 多级缓存架构: 使用多级缓存,例如本地缓存与分布式缓存结合,即使分布式缓存失效,本地缓存仍可提供一部分数据。

  2. 限流与熔断: 在缓存失效时,可以考虑实施限流措施,确保只有部分请求能够访问数据库,以保护数据库免受过大的请求压力。

  3. 监控与预警: 设置监控机制,及时监测缓存的状态,一旦发现缓存失效情况,能够及时预警和采取措施。

  4. 高可用架构: 使用主从复制、集群等高可用架构,保证Redis本身的稳定性,减少由于Redis节点故障引发的问题。

牛客网项目 SpringBoot

功能列表
部署架构

MyBatis

MyBatis 是一个用于简化数据库操作的持久层框架。它在 Java 应用程序和数据库之间建立了一个桥梁,使得开发者可以更轻松地进行数据库的访问和操作。MyBatis 的核心目标是将数据库操作从 Java 代码中解耦出来,同时提供了一些灵活的方式来执行数据库查询和修改

MyBatis 的工作方式是这样的:你可以编写原生的 SQL 查询语句,然后使用 MyBatis 将这些查询语句与 Java 对象进行映射。这样,你就可以用面向对象的方式来操作数据库,而不必直接处理数据库连接、结果集等繁琐的细节。

MyBatis 有一些优点,比如可以灵活地编写和调整 SQL 查询,不会对应用程序或数据库现有的设计造成影响。它可以帮助减少大量的重复代码,提高开发效率。同时,MyBatis 也支持与 Spring 等其他框架集成,使整个开发过程更加便捷。

然而,使用 MyBatis 也有一些注意事项。比如,你需要学习如何编写 SQL 查询语句,有时候也需要处理一些数据库移植性的问题,因为 SQL 语句可能会与特定数据库相关。

总的来说,MyBatis 是一个用于简化数据库操作的框架,通过提供灵活的 SQL 编程方式,帮助开发者更有效地进行数据库访问,同时提供了对象关系映射的能力,使得数据库操作变得更加便捷和可维护。

优点:

  1. 灵活的 SQL 编程:MyBatis 允许开发者编写原生的 SQL 查询语句,这使得查询非常灵活。这样做不会影响应用程序的结构或数据库设计,因为 SQL 语句被放在 XML 文件中,从而解耦了代码和查询,便于统一管理。

  2. 减少代码量:相较于传统的 JDBC,MyBatis 可以大大减少代码量,从而简化了数据库访问的操作。你不需要手动管理连接,释放资源等操作,MyBatis 会处理这些细节。

  3. 数据库兼容性:MyBatis 使用 JDBC 连接数据库,因此它与支持 JDBC 的各种数据库兼容良好。只要数据库支持 JDBC,MyBatis 就可以工作。

  4. 与 Spring 集成:MyBatis 很容易与 Spring 框架集成,这意味着你可以将 MyBatis 用于你的 Spring 项目中,使数据库访问更加方便。

  5. 映射标签支持:MyBatis 提供了映射标签,允许你定义对象与数据库之间的映射关系,从而实现了对象关系映射(ORM)的功能。

缺点:

  1. SQL 编写工作量:使用 MyBatis,需要开发者自行编写 SQL 语句。特别是在涉及多个字段、关联表等情况下,编写复杂的 SQL 查询可能需要一定的技能和耐心。

  2. 数据库依赖:MyBatis 的 SQL 语句是针对具体数据库编写的,这导致了一定程度上的数据库移植性差。如果你想要更换数据库,可能需要修改和适应一些 SQL 语句。

如何整合MyBatis

在IDEA中基于SpringBoot的框架中整合MyBatis.

  1. 创建Spring Boot项目: 使用IDEA创建一个新的Spring Boot项目,确保已选择相应的依赖,包括Spring Web、Spring Boot DevTools、MyBatis等。

  2. 配置数据库连接: 在application.propertiesapplication.yml文件中添加数据库连接配置,包括数据库URL、用户名、密码等。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_database
    username: your_username
    password: your_password
  1. 创建实体类: 创建Java类来表示数据库中的表,添加注解以映射数据库字段和实体属性。
package com.example.demo.model;

public class User {
    private Long id;
    private String username;
    private String email;

    // Getters and setters
}
  1. 创建MyBatis Mapper接口: 创建一个接口来定义数据库操作方法,使用@Mapper注解标记该接口。
package com.example.demo.mapper;

import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User getUserById(Long id);
    void insertUser(User user);
}
  1. 创建MyBatis Mapper XML文件: 在resources目录下创建与Mapper接口对应的XML文件,定义SQL语句和映射关系。
<!-- src/main/resources/com/example/demo/mapper/UserMapper.xml -->
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getUserById" resultType="com.example.demo.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    <insert id="insertUser" parameterType="com.example.demo.model.User">
        INSERT INTO users (username, email) VALUES (#{username}, #{email})
    </insert>
</mapper>
  1. 编写Service层: 创建Service层来调用MyBatis Mapper接口,处理业务逻辑。
package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserMapper userMapper;

    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public User getUserById(Long id) {
        return userMapper.getUserById(id);
    }

    public void insertUser(User user) {
        userMapper.insertUser(user);
    }
}
  1. 创建Controller层: 创建一个Controller类来处理HTTP请求,并调用Service层进行业务处理。
package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @PostMapping
    public void createUser(@RequestBody User user) {
        userService.insertUser(user);
    }
}

参考链接:
Offer来了:Java面试核心知识点精讲(原理篇)
Echo 前后端不分离的开源社区系统
Isolation Level in Rails

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容