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 <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) { String url = "jdbc:sqlite: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" ); 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 ); ps.setObject(2 , 1 ); ps.setObject(3 , "Bob" ); ps.setObject(4 , "M" ); int n = ps.executeUpdate(); } }
更新 更新操作是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" ); 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 ); 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 ); 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 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 (?, ?, ?, ?)" )) { 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(); } int [] ns = ps.executeBatch(); for (int n : ns) { System.out.println(n + " inserted." ); } }
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) 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 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") 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") 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 { @Before("execution(public * com.mine.anno.services.UserService.*(..))") public void doAccessCheck () { System.err.println("[Before] do access check..." ); } @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 { @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; 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 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 7.1.0-M2</version > <scope > compile</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 7.1.0-M2</version > <scope > compile</scope > </dependency > <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.dbjdbc.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) { return jdbcTemplate.execute((Connection conn) -> { 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 ( rs.getLong("id" ), rs.getString("email" ), rs.getString("password" ), rs.getString("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) { return jdbcTemplate.execute("SELECT * FROM users WHERE name = ?" , (PreparedStatement ps) -> { ps.setObject(1 , name); try (var rs = ps.executeQuery()) { if (rs.next()) { return new User ( rs.getLong("id" ), rs.getString("email" ), rs.getString("password" ), rs.getString("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) { return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email = ?" , (ResultSet rs, int rowNum) -> { return new User ( rs.getLong("id" ), rs.getString("email" ), rs.getString("password" ), rs.getString("name" )); }, email); }
在queryForObject()方法中,传入SQL以及SQL参数后,JdbcTemplate会自动创建PreparedStatement,自动执行查询并返回ResultSet,我们提供的RowMapper需要做的事情就是把ResultSet的当前行映射成一个JavaBean并返回。整个过程中,使用Connection、PreparedStatement和ResultSet都不需要我们手动管理。