Java 进阶内容

Lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FuncTest {
static void main() {
USB usb=new USB() {
@Override
public void open() {
System.out.println("USB Oopen");
}
};
}
}


@FunctionalInterface
interface USB{
void open();
}

可以减写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FuncTest {
static void main() {

UseUSB((s) -> System.out.println(s + " opened"));
}

static void UseUSB(USB usb){
usb.open("Mouse");
}
}

@FunctionalInterface
interface USB{
void open(String s);
}

Lombok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import lombok.Getter;
import lombok.Setter;

import java.io.IOException;

public class Main {
static void main() throws IOException {
Person p = new Person();
p.getName();
}
}

@Getter
@Setter
class Person{
int age;
String name;
}

Supplier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SupTest {
static void main() {
method(() -> {
int[] arr = {1, 2, 3, 2, 67, 3, 4};
Arrays.sort(arr);
return arr[arr.length - 1];
});
}

public static void method(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println("Max value is " + max);
}
}

Consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Arrays;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class SupTest {
static void main() {
method((s) -> {
System.out.println(s.length());
}, "Hello World");
}

public static void method(Consumer<String> s1, String s2) {
s1.accept(s2);
}
}

Function

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.function.Function;

public class SupTest {
static void main() {
method((s) -> {
return s.toString();
}, 2);
}

public static String method(Function<Integer,String > func, Integer num) {
return func.apply(num);
}
}

Stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest {
static void main() {
List<String> list = new ArrayList<>();
list.add("123");
list.add("298");
list.add("323");
list.add("478");
list.add("556");
list.add("634");

Stream<String> stream = list.stream();

Stream<String> stringStream = stream
.filter((s) -> s.contains("1"))
.filter((a) -> a.contains("2"));

stringStream.forEach((s)-> System.out.println(s));

}
}

JDBC

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.49.1.0</version>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.sql.*;

public class Main {
public static void main(String[] args) {
// JDBC 连接字符串格式: jdbc:sqlite:数据库文件路径
String url = "jdbc:sqlite:test.db"; // 会在当前目录创建 test.db 文件

String sql = "CREATE TABLE IF NOT EXISTS users ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "name TEXT NOT NULL,"
+ "email TEXT NOT NULL UNIQUE)";

try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement()) {
stmt.execute(sql);
System.out.println("表创建成功!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}

insert

1
2
3
4
5
6
7
8
9
10
11
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";

try (Connection conn = DriverManager.getConnection(url);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "张三");
pstmt.setString(2, "zhangsan@example.com");
pstmt.executeUpdate();
System.out.println("数据插入成功!");
} catch (SQLException e) {
e.printStackTrace();
}

select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String sql = "SELECT * FROM users";

try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {

while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.println(id + ", " + name + ", " + email);
}
} catch (SQLException e) {
e.printStackTrace();
}

完整查询代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
ps.setObject(1, "M"); // 注意:索引从1开始
ps.setObject(2, 3);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
long id = rs.getLong("id");
long grade = rs.getLong("grade");
String name = rs.getString("name");
String gender = rs.getString("gender");
}
}
}
}

插入

插入操作是INSERT,即插入一条新记录。通过JDBC进行插入,本质上也是用PreparedStatement执行一条SQL语句,不过最后执行的不是executeQuery(),而是executeUpdate()。示例代码如下:

1
2
3
4
5
6
7
8
9
10
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO students (id, grade, name, gender) VALUES (?,?,?,?)")) {
ps.setObject(1, 999); // 注意:索引从1开始
ps.setObject(2, 1); // grade
ps.setObject(3, "Bob"); // name
ps.setObject(4, "M"); // gender
int n = ps.executeUpdate(); // 1
}
}

更新

更新操作是UPDATE语句,它可以一次更新若干列的记录。更新操作和插入操作在JDBC代码的层面上实际上没有区别,除了SQL语句不同:

1
2
3
4
5
6
7
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
ps.setObject(1, "Bob"); // 注意:索引从1开始
ps.setObject(2, 999);
int n = ps.executeUpdate(); // 返回更新的行数
}
}

executeUpdate()返回数据库实际更新的行数。返回结果可能是正数,也可能是0(表示没有任何记录更新)。

删除

删除操作是DELETE语句,它可以一次删除若干行。和更新一样,除了SQL语句不同外,JDBC代码都是相同的:

1
2
3
4
5
6
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
ps.setObject(1, 999); // 注意:索引从1开始
int n = ps.executeUpdate(); // 删除的行数
}
}

事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Connection conn = openConnection();
try {
// 关闭自动提交:
conn.setAutoCommit(false);
// 执行多条SQL语句:
insert(); update(); delete();
// 提交事务:
conn.commit();
} catch (SQLException e) {
// 回滚事务:
conn.rollback();
} finally {
conn.setAutoCommit(true);
conn.close();
}

其中,开启事务的关键代码是conn.setAutoCommit(false),表示关闭自动提交。提交事务的代码在执行完指定的若干条SQL语句后,调用conn.commit()。要注意事务不是总能成功,如果事务提交失败,会抛出SQL异常(也可能在执行SQL语句的时候就抛出了),此时我们必须捕获并调用conn.rollback()回滚事务。最后,在finally中通过conn.setAutoCommit(true)把Connection对象的状态恢复到初始值。

实际上,默认情况下,我们获取到Connection连接后,总是处于“自动提交”模式,也就是每执行一条SQL都是作为事务自动执行的,这也是为什么前面几节我们的更新操作总能成功的原因:因为默认有这种“隐式事务”。只要关闭了Connection的autoCommit,那么就可以在一个事务中执行多条语句,事务以commit()方法结束。

如果要设定事务的隔离级别,可以使用如下代码:

1
2
// 设定隔离级别为READ COMMITTED:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。

在JDBC代码中,我们可以利用SQL数据库的这一特性,把同一个SQL但参数不同的若干次操作合并为一个batch执行。我们以批量插入为例,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (Student s : students) {
ps.setString(1, s.name);
ps.setBoolean(2, s.gender);
ps.setInt(3, s.grade);
ps.setInt(4, s.score);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch();
for (int n : ns) {
System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
}
}

Spring 基础

IoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
整个工程结构如下:

spring-ioc-annoconfig
├── pom.xml
└── src
└── main
└── java
└── com
└── itranswarp
└── learnjava
├── AppConfig.java
└── service
├── MailService.java
├── User.java
└── UserService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Component
public class MailService {
private ZoneId zoneId = ZoneId.systemDefault();

public void setZoneId(ZoneId zoneId) {
this.zoneId = zoneId;
}

public String getTime() {
return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
}

@Component
public class UserService {
@Autowired
private MailService mailService;

public void setMailService(MailService mailService) {
this.mailService = mailService;
System.out.println(this.mailService);
}

public void printMailService(){
System.out.println(this.mailService);
}
}

@Configuration
@ComponentScan
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.printMailService();
}
}

使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:

  • 每个Bean被标注为@Component并正确使用@Autowired注入;
  • 配置类被标注为@Configuration和@ComponentScan;
  • 所有Bean均在指定包以及子包内。

使用@ComponentScan非常方便,但是,我们也要特别注意包的层次结构。通常来说,启动配置AppConfig位于自定义的顶层包(例如com.itranswarp.learnjava),其他Bean按类别放入子包。

Scope

对于Spring容器来说,当我们把一个Bean标记为@Component后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。

还有一种Bean,我们每次调用getBean(Class),容器都返回一个新的实例,这种Bean称为Prototype(原型),它的生命周期显然和Singleton不同。声明一个Prototype的Bean时,需要添加一个额外的@Scope注解:

1
2
3
4
5
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
...
}

可选注入

默认情况下,当我们标记了一个@Autowired后,Spring如果没有找到对应类型的Bean,它会抛出NoSuchBeanDefinitionException异常。

可以给@Autowired增加一个required = false的参数:

1
2
3
4
5
6
@Component
public class MailService {
@Autowired(required = false)
ZoneId zoneId = ZoneId.systemDefault();
...
}

创建第三方Bean

如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?

答案是我们自己在@Configuration类中编写一个Java方法创建并返回它,注意给方法标记一个@Bean注解:

1
2
3
4
5
6
7
8
9
@Configuration
@ComponentScan
public class AppConfig {
// 创建一个Bean:
@Bean
ZoneId createZoneId() {
return ZoneId.of("Z");
}
}

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
String str = context.getBean("getSomeString", String.class);
System.out.println(str);
}

@Bean
String getSomeString(){
return "Hello WOrld";
}
}

初始化和销毁

有些时候,一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个init()方法进行初始化,定义一个shutdown()方法进行清理,然后,引入JSR-250定义的Annotation:

  • jakarta.annotation:jakarta.annotation-api:2.1.1

在Bean的初始化和清理方法上标记@PostConstruct和@PreDestroy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MailService {
@Autowired(required = false)
ZoneId zoneId = ZoneId.systemDefault();

@PostConstruct
public void init() {
System.out.println("Init mail service with zoneId = " + this.zoneId);
}

@PreDestroy
public void shutdown() {
System.out.println("Shutdown mail service");
}
}

使用别名

默认情况下,对一种类型的Bean,容器只创建一个实例。但有些时候,我们需要对一种类型的Bean创建多个实例。例如,同时连接多个数据库,就必须创建多个DataSource实例。

这个时候,需要给每个Bean添加不同的名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan
public class AppConfig {
@Bean("z")
ZoneId createZoneOfZ() {
return ZoneId.of("Z");
}

@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() {
return ZoneId.of("UTC+08:00");
}
}

可以用@Bean("name")指定别名,也可以用@Bean+@Qualifier("name")指定别名。

意思是期待找到唯一的ZoneId类型Bean,但是找到两。因此,注入时,要指定Bean的名称:

1
2
3
4
5
6
7
@Component
public class MailService {
@Autowired(required = false)
@Qualifier("z") // 指定注入名称为"z"的ZoneId
ZoneId zoneId = ZoneId.systemDefault();
...
}

使用FactoryBean

我们在设计模式的工厂方法中讲到,很多时候,可以通过工厂模式创建对象。Spring也提供了工厂模式,允许定义一个工厂,然后由工厂创建真正的Bean。

用工厂模式创建Bean需要实现FactoryBean接口。我们观察下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {

String zone = "Z";

@Override
public ZoneId getObject() throws Exception {
return ZoneId.of(zone);
}

@Override
public Class<?> getObjectType() {
return ZoneId.class;
}
}

当一个Bean实现了FactoryBean接口后,Spring会先实例化这个工厂,然后调用getObject()创建真正的Bean。getObjectType()可以指定创建的Bean的类型,因为指定类型不一定与实际类型一致,可以是接口或抽象类。

因此,如果定义了一个FactoryBean,要注意Spring创建的Bean实际上是这个FactoryBean的getObject()方法返回的Bean。为了和普通Bean区分,我们通常都以XxxFactoryBean命名。

由于可以用@Bean方法创建第三方Bean,本质上@Bean方法就是工厂方法,所以,FactoryBean已经用得越来越少了。

注入配置

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties
public class AppConfig {
@Value("${app.zone:Z}")
String zoneId;

@Bean
ZoneId createZoneId() {
return ZoneId.of(zoneId);
}
}

Spring容器看到@PropertySource("app.properties")注解后,自动读取这个配置文件,然后,我们使用@Value正常注入:

1
2
@Value("${app.zone:Z}")
String zoneId;

注意注入的字符串语法,它的格式如下:

  • "${app.zone}"表示读取key为app.zone的value,如果key不存在,启动将报错;
  • "${app.zone:Z}"表示读取key为app.zone的value,但如果key不存在,就使用默认值Z。

Spring AOP

AOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Component
public class MailService {
private ZoneId zoneId = ZoneId.systemDefault();

public void setZoneId(ZoneId zoneId) {
this.zoneId = zoneId;
}

public String getTime() {
return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
}
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserService {
@Autowired
private MailService mailService;

public void setMailService(MailService mailService) {
this.mailService = mailService;
System.out.println(this.mailService);
}

public void printMailService(){
System.out.println(this.mailService);
}
}
@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个方法前执行:
@Before("execution(public * com.mine.anno.services.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}

// 在执行MailService的每个方法前后执行:
@Around("execution(public * com.mine.anno.services.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MailService mailService = context.getBean(MailService.class);
mailService.getTime();
UserService userService = context.getBean(UserService.class);
userService.printMailService();
}
}

@interface

为了监控应用程序的性能,我们定义一个性能监控的注解:

1
2
3
4
5
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
String value();
}

在需要被监控的关键方法上标注该注解:

1
2
3
4
5
6
7
8
9
@Component
public class UserService {
// 监控register()方法性能:
@MetricTime("register")
public User register(String email, String password, String name) {
...
}
...
}

然后,我们定义MetricAspect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Aspect
@Component
public class MetricAspect {
@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
String name = metricTime.value();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long t = System.currentTimeMillis() - start;
// 写入日志或发送至JMX:
System.err.println("[Metrics] " + name + ": " + t + "ms");
}
}
}

注意metric()方法标注了@Around("@annotation(metricTime)"),它的意思是,符合条件的目标方法是带有@MetricTime注解的方法,因为metric()方法参数类型是MetricTime(注意参数名是metricTime不是MetricTime),我们通过它获取性能监控的名称。

有了@MetricTime注解,再配合MetricAspect,任何Bean,只要方法标注了@MetricTime注解,就可以自动实现性能监控。运行代码,输出结果如下:

1
2
Welcome, Bob!
[Metrics] register: 16ms

Spring JDBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- Source: https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.1.0-M2</version>
<scope>compile</scope>
</dependency>

<!-- Source: https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>7.1.0-M2</version>
<scope>compile</scope>
</dependency>

<!-- Source: https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>7.1.0-M2</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>

config.properties

1
2
3
jdbc.url=jdbc:sqlite:test.db
jdbc.username=
jdbc.password=

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
@ComponentScan
@PropertySource("config.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}

@Value("${jdbc.url}")
String jdbcUrl;

@Value("${jdbc.username}")
String jdbcUsername;

@Value("${jdbc.password}")
String jdbcPassword;

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.sqlite.JDBC");
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUsername);
dataSource.setPassword(jdbcPassword);
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class Init {
@Autowired
JdbcTemplate template;

@PostConstruct
void initDatabase() {
System.out.println("Post construct");
template.update("""
CREATE TABLE IF NOT EXISTS COMPANY (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
NAME TEXT NOT NULL,
AGE INTEGER NOT NULL,
ADDRESS CHAR(50),
SALARY REAL DEFAULT 0.0
);
""");
}
}

现在,所有准备工作都已完毕。我们只需要在需要访问数据库的Bean中,注入JdbcTemplate即可:

1
2
3
4
5
6
@Component
public class UserService {
@Autowired
JdbcTemplate jdbcTemplate;
...
}

JdbcTemplate用法

Spring提供的JdbcTemplate采用Template模式,提供了一系列以回调为特点的工具方法,目的是避免繁琐的try...catch语句。

我们以具体的示例来说明JdbcTemplate的用法。

首先我们看T execute(ConnectionCallback<T> action)方法,它提供了Jdbc的Connection供我们使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public User getUserById(long id) {
// 注意传入的是ConnectionCallback:
return jdbcTemplate.execute((Connection conn) -> {
// 可以直接使用conn实例,不要释放它,回调结束后JdbcTemplate自动释放:
// 在内部手动创建的PreparedStatement、ResultSet必须用try(...)释放:
try (var ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
ps.setObject(1, id);
try (var rs = ps.executeQuery()) {
if (rs.next()) {
return new User( // new User object:
rs.getLong("id"), // id
rs.getString("email"), // email
rs.getString("password"), // password
rs.getString("name")); // name
}
throw new RuntimeException("user not found by id.");
}
}
});
}

我们再看T execute(String sql, PreparedStatementCallback<T> action)的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public User getUserByName(String name) {
// 需要传入SQL语句,以及PreparedStatementCallback:
return jdbcTemplate.execute("SELECT * FROM users WHERE name = ?", (PreparedStatement ps) -> {
// PreparedStatement实例已经由JdbcTemplate创建,并在回调后自动释放:
ps.setObject(1, name);
try (var rs = ps.executeQuery()) {
if (rs.next()) {
return new User( // new User object:
rs.getLong("id"), // id
rs.getString("email"), // email
rs.getString("password"), // password
rs.getString("name")); // name
}
throw new RuntimeException("user not found by id.");
}
});
}

最后,我们看T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public User getUserByEmail(String email) {
// 传入SQL,参数和RowMapper实例:
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email = ?",
(ResultSet rs, int rowNum) -> {
// 将ResultSet的当前行映射为一个JavaBean:
return new User( // new User object:
rs.getLong("id"), // id
rs.getString("email"), // email
rs.getString("password"), // password
rs.getString("name")); // name
},
email);
}

在queryForObject()方法中,传入SQL以及SQL参数后,JdbcTemplate会自动创建PreparedStatement,自动执行查询并返回ResultSet,我们提供的RowMapper需要做的事情就是把ResultSet的当前行映射成一个JavaBean并返回。整个过程中,使用Connection、PreparedStatement和ResultSet都不需要我们手动管理。


Java 进阶内容
https://taylorandtony.github.io/2026/10/10/Java-进阶内容/
作者
TaylorAndTony
发布于
2026年10月10日
许可协议