Spring Boot(5)--Accessing Relational Data using JDBC with Spring (Lambda,匿名内部类,H2数据库)

springboot的总官方学习文档

本次本章对应的官方文档

按照老方法,GIThub 克隆到本地,然后Eclipse打开,下载的文件夹,导入项目
在这里插入图片描述

创建一个Customer对象

您将使用的简单数据访问逻辑管理客户的名字和姓氏。要在应用程序级别表示此数据,请创建一个Customer类
src/main/java/com/example/relationaldataaccess/Customer.java对应代码:

package com.example.relationaldataaccess;

public class Customer {
	private long id;
	private String firstName, lastName;

	public Customer(long id, String firstName, String lastName) {
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}
	
	//因为所有类都继承自Object,这个里面已经定义了这个方法了,所以重载
	@Override
	public String toString() {
		return String.format(
				"Customer[id=%d, firstName='%s', lastName='%s']",
				id, firstName, lastName);
	}

	// getters & setters omitted for brevity  为了简便省略了
}

存储和检索数据

Spring提供了一个名为的模板类JdbcTemplate,可以轻松使用SQL关系数据库和JDBC。大多数JDBC代码都忙于于资源获取,连接管理,异常处理和常规错误检查中,而这些检查与代码的意图完全无关。该JdbcTemplate为你负责这一切的,您要做的就是专注于手头的任务。
代码 显示了一个可以通过JDBC存储和检索数据的类src/main/java/com/example/relationaldataaccess/RelationalDataAccessApplication.java:

main() 方法使用 Spring Boot’s SpringApplication.run() 方法来运行一个 应用程序

Spring Boot支持H2(内存中关系数据库引擎)并自动创建连接。因为使用了spring-jdbc,所以Spring Boot会自动创建一个JdbcTemplate。@Autowired JdbcTemplate 将自动加载并使其可用。

Application这个类实现Spring Boot的CommandLineRunner,这意味着它将在应用程序内容加载完毕后 执行run()方法

首先,使用 JdbcTemplate的execute方法来执行一些DDL-----数据库模式定义语言DDL(Data Definition Language),主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上。

其次,获取一个字符串列表,并使用Java 8流将它们分成Java数组中的名字/姓氏对。

然后使用JdbcTemplate的batchUpdate方法在新创建的表中添加一些记录。调用方法的第一个参数是查询字符串。最后一个参数(Object实例的数组)保存着查询的变量用来替换?字符。

对于单个插入语句,insert方法JdbcTemplate挺好。但是,对于多个插入,最好使用batchUpdate。

使用?的参数,来避免SQL注入攻击通过指示JDBC来绑定变量。(这个不太懂)

最后,使用query方法在表中搜索符合条件的记录。您再次使用?参数来为查询创建参数,并在进行调用时传递实际值。最后一个参数是Java 8 lambda,用于将每个结果行转换为新Customer对象。

Java 8 lambda很好地映射到了单个方法接口,例如Spring的RowMapper。如果使用Java 7或更早版本,则可以插入匿名接口实现,并使方法主体与lambda表达式的主体相同。

这里好好说一下,首先 Lambda 表达式(lambda expression)是一个匿名函数,即没有函数名的函数,引用菜鸟教程里面的说法
lambda 表达式的语法格式如下:

  • (parameters) -> expression

  • (parameters) ->{ statements; }

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

代码实战如下

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
      
      interface MathOperation {//接口
      int operation(int a, int b);//接口中的抽象方法
   }
  // 以往之中,我们定义一个接口最终都是为了让一个类实现这个接口,如下三行代码,可直接调用的静态方法不算
 //  class 类 implements 接口 {
  //  重写接口中方法
 //  }
 //函数式接口,指的是只有一个抽象方法的接口。函数式接口可以被隐式转换为Lambda表达式。
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int c(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
      // 类型声明  
      MathOperation addition = (int a, int b) -> a + b;
       //其实这里就是利用Lambda表达式实现实例化函数式接口的对象
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    

}

个人理解这个东西最方便的就是如果一些函数的参数的参数中有一个是函数式接口,我们就不需要再写一个匿名的内部类了,直接使用Lambda表达式实现实例化函数式接口的对象。
然后回到我们的代码中

jdbcTemplate.query(
				"SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" },
				(rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"))
		).forEach(customer -> log.info(customer.toString()));

首先是对query这个函数的解释
Query given SQL to create a prepared statement from SQL and a list of arguments to bind to the query, mapping each row to a result object via a RowMapper.
大体翻译成中文就是:查询给定的SQL以从SQL创建一个准备好的语句,并创建一个要绑定到查询的参数列表,通过行映射器将每一行映射到一个结果对象。

Parameters:
sql the SQL query to execute
args arguments to bind to the query(leaving it to the Prepared Statement to guess the corresponding SQL type);may also contain Sql ParameterValue objects which indicate notonly the argument value but also the SQL type and optionally the scale SQL查询语句中需要的参数
rowMapper a callback that will map one object per row 将会对每行映射到对象的回调函数

Returns:
the result List, containing mapped objects 一个包含映射对象的列表
如果使用匿名函数类实现就是

jdbcTemplate.query(
				"SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" },
				new RowMapper<Customer>() {  
            @Override  
            public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {  
            	Customer user = new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"));  
                return user;  
            }  
        }).forEach(customer -> log.info(customer.toString()));
		

(rs, rowNum) -> new Customer(rs.getLong(“id”), rs.getString(“first_name”), rs.getString(“last_name”))
就是对应了RowMapper 这个函数式接口的

最后最后还有一段代码要注意一下
forEach(customer -> log.info(customer.toString()))
本身forEach函数是需要一个action参数的,这个action参数是 Consumer这个函数式接口的(因为这个接口里面就包含了一个函数void accept(T t),而customer -> log.info(customer.toString())这句话直接重载了这个accept函数,并且函数void accept(T t)中的参数t 就是 customer这个参数,类型T自动匹配为query函数返回列表中的Consumer对象,accept函数内容就是log.info(customer.toString())

forEach(Consumer<? super Customer> action)
Performs the given action for each element of the Iterable until all elements have been processed or the action throws anexception。以迭代的方式,对每一个元素执行给定的行为
参数action: The action to be performed for each element
在这里插入图片描述
也是一个函数式的接口,所以可以直接使用Lambda表达式实现实例化函数式接口的对象。
当然也可以使用匿名类了,如下图

forEach(new Consumer<Customer>() {
        	@Override
        	public  void accept(Customer t) {
        		log.info(t.toString());
        	}
        });

所有尝试过的代码结合

package com.example.relationaldataaccess;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
import org.springframework.lang.Nullable;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@SpringBootApplication
public class RelationalDataAccessApplication implements CommandLineRunner {

	private static final Logger log = LoggerFactory.getLogger(RelationalDataAccessApplication.class);

	public static void main(String args[]) {
		SpringApplication.run(RelationalDataAccessApplication.class, args);
	}

	@Autowired
	JdbcTemplate jdbcTemplate;

	@Override
	public void run(String... strings) throws Exception {

		log.info("Creating tables");

		jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
		jdbcTemplate.execute("CREATE TABLE customers(" +
				"id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");

		// Split up the array of whole names into an array of first/last names
		List<Object[]> splitUpNames = Arrays.asList("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long").stream()
				.map(name -> name.split(" "))
				.collect(Collectors.toList());

		// Use a Java 8 stream to print out each tuple of the list
		splitUpNames.forEach(name -> log.info(String.format("Inserting customer record for %s %s", name[0], name[1])));

		// Uses JdbcTemplate's batchUpdate operation to bulk load data
		jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);

		log.info("Querying for customer records where first_name = 'Josh':");

//官网推荐的代码使用Lambda,不使用匿名内部类
//		jdbcTemplate.query(
//				"SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" },
//				(rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"))
//		).forEach(customer -> log.info(customer.toString()));
//		

//使用匿名内部类		
		jdbcTemplate.query(
				"SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" },
				new RowMapper<Customer>() {  
            @Override  
            public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {  
            	Customer user = new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"));  
                return user;  
            }  
        }).forEach(new Consumer<Customer>() {
        	@Override
        	public  void accept(Customer t) {
        		log.info(t.toString());
        	}
        });
		
		
		
		
//      ResultSet rs, int rowNum
		
//		public <T> List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
//			return result(query(sql, args, new RowMapperResultSetExtractor<>(rowMapper)));
//		}
		
	}
}

然后关于H2这个数据库,他就直接在内存中的,看做Mysql 不保存在硬盘中,直接在内存中,这个也太6了,并且也可以直接持久化保存(保存到硬盘),对此,我能666666

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值