ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# JDBI 教程 > 原文: [http://zetcode.com/db/jdbi/](http://zetcode.com/db/jdbi/) 在本教程中,我们展示了如何使用 JDBI 处理数据。 我们选择 MySQL 作为数据库。 ZetCode 拥有用于 MySQL Java 的完整电子书,其中包含 JDBI 章节: [MySQL Java 编程电子书](/ebooks/mysqljava/)。 [Tweet](https://twitter.com/share) JDBI 是建立在 JDBC 之上的便捷库。 它使数据库编程容易得多。 它管理异常。 它具有用于自动资源管理和将结果集映射到类的工具。 JDBI 在概念上类似于 Spring 的`JdbcTemplate`,为此 ZetCode 具有[教程](/db/jdbctemplate/)。 `DBI`实例通过`Handle`实例提供与数据库的连接。 `Handle`表示与数据库系统的连接; 它是 JDBC 连接对象的包装。 JDBI 提供了两种不同的样式 API:流利的样式和对象样式。 ## 在 MySQL 中创建数据库 在本节中,我们将在 MySQL 中创建一个新的`testdb`数据库。 我们使用`mysql`监视器来完成这项工作,但是我们也可以使用 NetBeans 数据库工具。 `cars_mysql.sql` ```java DROP TABLE IF EXISTS Cars; CREATE TABLE Cars(Id INT PRIMARY KEY AUTO_INCREMENT, Name TEXT, Price INT) ENGINE=InnoDB; INSERT INTO Cars(Name, Price) VALUES('Audi', 52642); INSERT INTO Cars(Name, Price) VALUES('Mercedes', 57127); INSERT INTO Cars(Name, Price) VALUES('Skoda', 9000); INSERT INTO Cars(Name, Price) VALUES('Volvo', 29000); INSERT INTO Cars(Name, Price) VALUES('Bentley', 350000); INSERT INTO Cars(Name, Price) VALUES('Citroen', 21000); INSERT INTO Cars(Name, Price) VALUES('Hummer', 41400); INSERT INTO Cars(Name, Price) VALUES('Volkswagen', 21600); ``` 这是在 MySQL 中创建`Cars`表的 SQL。 要创建数据库和表,我们使用`mysql`监视工具。 ```java $ sudo service mysql start ``` MySQL 用`sudo service mysql start`命令启动。 ```java $ mysql -u testuser -p ``` 我们使用`mysql`监视器连接到数据库。 ```java mysql> CREATE DATABASE testdb; Query OK, 1 row affected (0.02 sec) ``` 使用`CREATE DATABASE`语句创建一个新数据库。 ```java mysql> USE testdb; mysql> SOURCE cars_mysql.sql ``` 使用`source`命令,加载并执行`cars_mysql.sql`文件。 ```java mysql> SELECT * FROM Cars; +----+------------+--------+ | Id | Name | Price | +----+------------+--------+ | 1 | Audi | 52642 | | 2 | Mercedes | 57127 | | 3 | Skoda | 9000 | | 4 | Volvo | 29000 | | 5 | Bentley | 350000 | | 6 | Citroen | 21000 | | 7 | Hummer | 41400 | | 8 | Volkswagen | 21600 | +----+------------+--------+ 8 rows in set (0.00 sec) ``` 我们验证数据。 ## `pom.xml`文件 这些示例将使用以下 Maven POM 文件: `pom.xml` ```java <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>JDBIEx</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.jdbi</groupId> <artifactId>jdbi</artifactId> <version>2.73</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> </dependencies> </project> ``` 我们已经为 JDBI 库和 MySQL 驱动程序定义了依赖项。 ## 流利的 API 在以下示例中,我们将使用 JDBI 流利 API 与 MySQL 数据库一起使用。 ### 取回所有汽车 在第一个示例中,我们从`Cars`表中提取所有汽车。 `JDBIEx.java` ```java package com.zetcode; import java.util.List; import java.util.Map; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Query; public class JDBIEx { public static void main(String[] args) { Handle handle = null; DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); String sql = "SELECT * FROM Cars"; try { handle = dbi.open(); Query<Map<String, Object>> q = handle.createQuery(sql); List<Map<String, Object>> l = q.list(); for (Map<String, Object> m : l) { System.out.printf("%d ", m.get("Id")); System.out.printf("%s ", m.get("Name")); System.out.println(m.get("Price")); } } finally { if (handle != null) { handle.close(); } } } } ``` 该示例连接到`testdb`数据库,并从`Cars`表中检索所有汽车。 ```java DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); ``` 使用`DBI`类为数据库创建一个访问点。 ```java handle = dbi.open(); ``` 使用`DBI`的`open()`方法创建数据库的`Handle`。 它表示与数据库的连接。 使用`DriverManager`创建到数据库的连接。 ```java Query<Map<String, Object>> q = handle.createQuery(sql); ``` 使用`createQuery()`方法创建`Query`对象。 ```java List<Map<String, Object>> l = q.list(); ``` 从查询对象中,我们获得键/值对的列表。 ```java for (Map<String, Object> m : l) { System.out.printf("%d ", m.get("Id")); System.out.printf("%s ", m.get("Name")); System.out.println(m.get("Price")); } ``` 我们遍历列表并打印所有列。 ```java } finally { if (handle != null) { handle.close(); } } ``` 最后,我们关闭手柄。 ```java 1 Audi 52642 2 Mercedes 57127 3 Skoda 9000 4 Volvo 29000 5 Bentley 350000 6 Citroen 21000 7 Hummer 41400 8 Volkswagen 21600 ``` 这是示例的输出。 ### 通过 ID 检索汽车 在下一个示例中,我们通过`Cars`表中的 ID 提取汽车名称。 `JDBIEx2.java` ```java package com.zetcode; import java.util.Map; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Query; import org.skife.jdbi.v2.util.StringColumnMapper; public class JDBIEx2 { public static void main(String[] args) { Handle handle = null; DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); try { handle = dbi.open(); String sql = "SELECT Name FROM Cars WHERE Id = ?"; Query<Map<String, Object>> q = handle.createQuery(sql); q.bind(0, 1); String carName = q.map(StringColumnMapper.INSTANCE).first(); System.out.println(carName); } finally { if (handle != null) { handle.close(); } } } } ``` 在示例中,我们从`Cars`表中选择汽车名称。 SQL 查询采用一个稍后绑定的参数。 ```java String sql = "SELECT Name FROM Cars WHERE Id = ?"; ``` 这是用于从表中选择汽车名称的 SQL 代码。 问号是一个令牌,稍后将在代码中填充。 ```java Query<Map<String, Object>> q = handle.createQuery(sql); ``` 从 SQL 语句创建一个新的`Query`对象。 ```java q.bind(0, 1); ``` 使用`bind()`方法,我们绑定缺少的参数。 参数在位置上绑定。 ```java String carName = q.map(StringColumnMapper.INSTANCE).first(); ``` 我们将带有`StringColumnMapper`的结果集的列映射到字符串类型。 `first()`方法用于返回一个值。 ```java System.out.println(carName); ``` 汽车的名称被打印到控制台上。 ### 数据源 在此示例中,我们使用数据源连接到数据库。 数据源的使用可以提高应用的性能和可伸缩性。 `db.properties` ```java # mysql properties mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/testdb mysql.username=testuser mysql.password=test623 ``` 在`db.properties`文件中,我们具有连接属性。 ![Database properties](https://img.kancloud.cn/f7/c6/f7c64b97102fab51455a73e2fa06ae75_213x55.jpg) 图:数据库属性 该文件放置在项目的`Resources`目录中。 `JDBIEx3.java` ```java package com.zetcode; import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import java.util.Properties; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Query; import org.skife.jdbi.v2.util.IntegerColumnMapper; public class JDBIEx3 { public static MysqlDataSource getMySQLDataSource() throws FileNotFoundException, IOException { Properties props = new Properties(); FileInputStream fis = null; MysqlDataSource ds = null; fis = new FileInputStream("src/main/Resources/db.properties"); props.load(fis); ds = new MysqlConnectionPoolDataSource(); ds.setURL(props.getProperty("mysql.url")); ds.setUser(props.getProperty("mysql.username")); ds.setPassword(props.getProperty("mysql.password")); return ds; } public static void main(String[] args) throws IOException { Handle handle = null; MysqlDataSource ds = getMySQLDataSource(); DBI dbi = new DBI(ds); try { handle = dbi.open(); String sql = "SELECT Price FROM Cars WHERE Id = ?"; Query<Map<String, Object>> q = handle.createQuery(sql); q.bind(0, 1); Integer price = q.map(IntegerColumnMapper.WRAPPER).first(); System.out.println(price); } finally { if (handle != null) { handle.close(); } } } } ``` 该示例选择通过 ID 查找的汽车价格。 ```java fis = new FileInputStream("src/main/Resources/db.properties"); props.load(fis); ``` 我们从`Resources`目录加载属性。 ```java ds = new MysqlConnectionPoolDataSource(); ds.setURL(props.getProperty("mysql.url")); ds.setUser(props.getProperty("mysql.username")); ds.setPassword(props.getProperty("mysql.password")); ``` 创建了`MysqlConnectionPoolDataSource`。 我们从属性文件中设置参数。 ```java Integer price = q.map(IntegerColumnMapper.WRAPPER).first(); ``` 由于 SQL 查询返回整数,因此我们使用`IntegerColumnMapper`类。 ### `withHandle()`方法 `DBI`类具有称为`withHandle()`的便捷方法,该方法管理句柄的生命周期,并将其提供给回调以供客户端使用。 `JDBIEx4.java` ```java package com.zetcode; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.util.IntegerColumnMapper; public class JDBIEx4 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); String sql = "SELECT Price FROM Cars WHERE Id = :id"; int id = 3; Integer price = dbi.withHandle((Handle h) -> { return h.createQuery(sql) .map(IntegerColumnMapper.WRAPPER) .bind("id", id) .first(); }); System.out.println(price); } } ``` 该示例选择由其 ID 标识的汽车价格。 ```java String sql = "SELECT Price FROM Cars WHERE Id = :id"; ``` 此 SQL 查询使用命名参数。 ```java Integer price = dbi.withHandle((Handle h) -> { return h.createQuery(sql) .map(IntegerColumnMapper.WRAPPER) .bind("id", id) .first(); }); ``` 创建和执行查询时,我们不必担心关闭句柄。 ### 映射自定义类 可以将自定义类映射到结果集。 映射类必须实现`ResultSetMapper<T>`接口。 `Car.java` ```java package com.zetcode; public class Car { private Long Id; private String Name; private int Price; public Car(Long Id, String Name, int Price) { this.Id = Id; this.Name = Name; this.Price = Price; } public Long getId() { return Id; } public void setId(Long Id) { this.Id = Id; } public String getName() { return Name; } public void setName(String Name) { this.Name = Name; } public int getPrice() { return Price; } public void setPrice(int Price) { this.Price = Price; } @Override public String toString() { return "Car{" + "Id=" + Id + ", Name=" + Name + ", Price=" + Price + '}'; } } ``` 这是一个自定义`Car`类,我们将结果集映射到该类。 `CarMapper.java` ```java package com.zetcode; import java.sql.ResultSet; import java.sql.SQLException; import org.skife.jdbi.v2.StatementContext; import org.skife.jdbi.v2.tweak.ResultSetMapper; public class CarMapper implements ResultSetMapper<Car> { @Override public Car map(int idx, ResultSet rs, StatementContext ctx) throws SQLException { return new Car(rs.getLong("Id"), rs.getString("Name"), rs.getInt("Price")); } } ``` 我们提供了映射类。 它返回一个新的`Car`对象,其中填充了结果集中的数据。 `JDBIEx5.java` ```java package com.zetcode; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; public class JDBIEx5 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); String sql = "SELECT * FROM Cars WHERE Id = :id"; int id = 3; Car car = dbi.withHandle((Handle h) -> { return h.createQuery(sql) .map(new CarMapper()) .bind("id", id) .first(); }); System.out.println(car); } } ``` 该示例从由其 ID 标识的表中选择一个`Car`对象。 ```java Car car = dbi.withHandle((Handle h) -> { return h.createQuery(sql) .map(new CarMapper()) .bind("id", id) .first(); }); ``` 自定义`CarMapper`对象将传递给`map()`方法。 ### 批量操作 批处理允许我们将相关的 SQL 语句分组为批,然后通过一次调用将其提交到数据库。 这可以大大提高我们的应用的性能。 批处理操作不是原子操作; 他们没有提供全部或全部解决方案。 例如,如果我们创建不正确的`INSERT`语句,它将失败,但是将执行其他`INSERT`语句。 `JDBIEx6.java` ```java package com.zetcode; import org.skife.jdbi.v2.Batch; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; public class JDBIEx6 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); Handle handle = dbi.open(); Batch batch = handle.createBatch(); batch.add("DROP TABLE IF EXISTS Friends"); batch.add("CREATE TABLE Friends(Id INT AUTO_INCREMENT PRIMARY KEY, Name TEXT)"); batch.add("INSERT INTO Friends(Name) VALUES ('Monika')"); batch.add("INSERT INTO Friends(Name) VALUES ('Tom')"); batch.add("INSERT INTO Friends(Name) VALUES ('Jane')"); batch.add("INSERT INTO Friends(Name) VALUES ('Robert')"); batch.execute(); } } ``` 该示例创建一个新的`Friends`表。 SQL 命令被分组为一个批处理操作。 ```java Batch batch = handle.createBatch(); ``` `Batch`代表一组未预备语句; 它是使用`createBatch()`方法创建的。 ```java batch.add("DROP TABLE IF EXISTS Friends"); ``` `add()`方法将语句添加到批处理中。 ```java batch.execute(); ``` 批处理通过`execute()`方法执行。 ### 事务 事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。 还要注意,在 MySQL 中,DDL 语句(例如`DROP TABLE`和`CREATE TABLE`)会导致对事务的隐式提交。 `JDBIEx7.java` ```java package com.zetcode; import org.skife.jdbi.v2.Batch; import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.TransactionStatus; import org.skife.jdbi.v2.VoidTransactionCallback; public class JDBIEx7 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); dbi.inTransaction(new VoidTransactionCallback() { @Override protected void execute(Handle handle, TransactionStatus status) throws Exception { Batch batch = handle.createBatch(); batch.add("DROP TABLE IF EXISTS Friends"); batch.add("CREATE TABLE Friends(Id INT AUTO_INCREMENT PRIMARY KEY, Name TEXT)"); batch.add("INSERT INTO Friends(Name) VALUES ('Monika')"); batch.add("INSERT INTO Friends(Name) VALUES ('Tom')"); batch.add("INSERT INTO Friends(Name) VALUES ('Jane')"); batch.add("INSERT INTO Friends(Name) VALUES ('Robert')"); batch.execute(); } }); } } ``` 该示例将批处理操作置于事务中。 由于 MYSQL 中 DDL 语句的隐式提交,因此只有`INSERT`语句处于全有或全无模式。 ```java dbi.inTransaction(new VoidTransactionCallback() { @Override protected void execute(Handle handle, TransactionStatus status) throws Exception { ... } }); ``` 使用`inTransaction()`方法创建事务。 `VoidTransactionCallback`是不返回值的事务回调。 ## SQL 对象 API SQL 对象 API 为常见的 JDBI 操作提供了一种声明性机制。 要使用 SQL 对象 API,我们创建带有注解的接口或抽象类,例如`@SqlQuery`或`@SqlUpdate`。 ### 简单的例子 我们创建了一个示例,其中将使用 SQL 对象 API 创建简单查询。 ```java <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.8</version> </dependency> ``` 在示例中,我们还使用了`lombok`库,该库减少了一些样板代码。 `Car.java` ```java package com.zetcode; import lombok.Data; @Data public class Car { private final Long Id; private final String Name; private final int Price; } ``` `Car`类装饰有 Lombok 的`@Data`注解。 它将自动创建获取器和设置器方法,`equals()`方法,`toString()`方法,`hashCode()`方法和参数构造器。 `CarMapper.java` ```java package com.zetcode; import java.sql.ResultSet; import java.sql.SQLException; import org.skife.jdbi.v2.StatementContext; import org.skife.jdbi.v2.tweak.ResultSetMapper; public class CarMapper implements ResultSetMapper<Car> { @Override public Car map(int idx, ResultSet rs, StatementContext ctx) throws SQLException { return new Car(rs.getLong("Id"), rs.getString("Name"), rs.getInt("Price")); } } ``` `CarMapper`将结果集映射到`Car`类。 `MyDAO.java` ```java package com.zetcode; import org.skife.jdbi.v2.sqlobject.Bind; import org.skife.jdbi.v2.sqlobject.SqlQuery; import org.skife.jdbi.v2.sqlobject.customizers.Mapper; public interface MyDAO { @SqlQuery("SELECT * FROM Cars WHERE Id = :id") @Mapper(CarMapper.class) Car findById(@Bind("id") int id); @SqlQuery("SELECT COUNT(Id) FROM Cars") int countCars(); } ``` 在这里,我们有一个`MyDAO`接口,装饰有两个`@SqlQuery`注解。 这些方法通过其 ID 查找汽车并计算表中的所有汽车。 ```java @SqlQuery("SELECT * FROM Cars WHERE Id = :id") ``` `@SqlQuery`注解指示该方法执行指定的查询。 ```java @Mapper(CarMapper.class) ``` `@Mapper`指定查询方法上的结果集映射器。 ```java Car findById(@Bind("id") int id); ``` `@Bind`注解将方法的参数绑定到 SQL 查询参数。 `JDBIEx8.java` ```java package com.zetcode; import org.skife.jdbi.v2.DBI; public class JDBIEx8 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); int id = 3; MyDAO dao = dbi.onDemand(MyDAO.class); Car car = dao.findById(id); System.out.println(car); int nCars = dao.countCars(); System.out.printf("There are %d cars in the table", nCars); } } ``` 在此客户端应用中,我们找到 ID 等于 3 的汽车,并计算表中的所有汽车。 ```java MyDAO dao = dbi.onDemand(MyDAO.class); ``` `onDemand()`方法创建一个新的 sql 对象,该对象将分别根据需要和可以从该 dbi 实例获取和释放连接。 我们不应该显式关闭此 sql 对象。 ```java Car car = dao.findById(id); ``` 我们得到了具有指定 ID 的汽车。 ```java int nCars = dao.countCars(); ``` 我们在数据库表中计算汽车的数量。 ### 事务 在 SQL 对象 API 中,我们可以使用`@Transaction`注解创建事务。 `authors_books.sql` ```java CREATE TABLE IF NOT EXISTS Authors(Id BIGINT PRIMARY KEY AUTO_INCREMENT, Name VARCHAR(25)) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS Books(Id BIGINT PRIMARY KEY AUTO_INCREMENT, AuthorId BIGINT, Title VARCHAR(100), FOREIGN KEY(AuthorId) REFERENCES Authors(Id) ON DELETE CASCADE) ENGINE=InnoDB; ``` 对于此示例,我们创建两个表:`Authors`和`Books`。 `MyDAO.java` ```java package com.zetcode; import java.util.List; import org.skife.jdbi.v2.exceptions.TransactionFailedException; import org.skife.jdbi.v2.sqlobject.Bind; import org.skife.jdbi.v2.sqlobject.SqlQuery; import org.skife.jdbi.v2.sqlobject.SqlUpdate; import org.skife.jdbi.v2.sqlobject.Transaction; public abstract class MyDAO { @SqlUpdate("INSERT INTO Authors(Name) VALUES(:author)") public abstract void createAuthor(@Bind("author") String author); @SqlQuery("SELECT Id FROM Authors WHERE Name = :name") abstract long getAuthorId(@Bind("name") String name); @SqlUpdate("INSERT INTO Books(AuthorId, Title) VALUES(:authorId, :title)") abstract void insertBook(@Bind("authorId") Long authorId, @Bind("title") String title); @Transaction public void insertBooksForAuthor(String author, List<String> titles) { Long authorId = getAuthorId(author); if (authorId == null) { throw new TransactionFailedException("No author found"); } for (String title : titles) { insertBook(authorId, title); } } } ``` 我们有一个抽象的`MyDAO`类,其中利用了`@SqlUpdate`,`@SqlQuery`和`@Transaction`注解。 ```java @SqlUpdate("INSERT INTO Authors(Name) VALUES(:author)") public abstract void createAuthor(@Bind("author") String author); ``` 此方法添加了一个新作者。 ```java @SqlQuery("SELECT Id FROM Authors WHERE Name = :name") abstract long getAuthorId(@Bind("name") String name); ``` `getAuthorId()`用于获取作者的 ID。 当我们将新书插入`Books`表时,需要 ID。 ```java @SqlUpdate("INSERT INTO Books(AuthorId, Title) VALUES(:authorId, :title)") abstract void insertBook(@Bind("authorId") Long authorId, @Bind("title") String title); ``` `insertBook()`方法将一本书插入`Books`表中。 ```java @Transaction public void insertBooksForAuthor(String author, List<String> titles) { Long authorId = getAuthorId(author); if (authorId == null) { throw new TransactionFailedException("No author found"); } for (String title : titles) { insertBook(authorId, title); } } ``` `@Transaction`注解使`insertBooksForAuthor()`在事务内运行。 因此,要么插入所有书籍,要么不插入任何书籍。 `JDBIEx9.java` ```java package com.zetcode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.skife.jdbi.v2.DBI; public class JDBIEx9 { public static void main(String[] args) { DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb", "testuser", "test623"); List<Map<String, List<String>>> authorsBooks = new ArrayList<>(); Map<String, List<String>> autMap1 = new HashMap<>(); List<String> books1 = new ArrayList<>(); books1.add("Call of the Wild"); books1.add("Martin Eden"); books1.add("The Iron Heel"); books1.add("White Fang"); autMap1.put("Jack London", books1); Map<String, List<String>> autMap2 = new HashMap<>(); List<String> books2 = new ArrayList<>(); books2.add("Father Goriot"); books2.add("Colonel Chabert"); books2.add("Cousing Pons"); autMap2.put("Honore de Balzac", books2); authorsBooks.add(autMap1); authorsBooks.add(autMap2); MyDAO dao = dbi.onDemand(MyDAO.class); for (Map<String, List<String>> map : authorsBooks) { Set<String> ks = map.keySet(); for (String author : ks) { dao.createAuthor(author); List<String> titles = map.get(author); dao.insertBooksForAuthor(author, titles); } } } } ``` 该示例将两名作者及其书籍插入数据库。 在本教程中,我们介绍了 JDBI 库。 ZetCode 具有以下相关教程: [Java 教程](/lang/java/), [MySQL Java 教程](/db/mysqljava/)和 [MySQL 教程](/databases/mysqltutorial/)。