Panduan untuk Apache Commons DbUtils

1. Gambaran keseluruhan

Apache Commons DbUtils adalah perpustakaan kecil yang menjadikan kerja dengan JDBC menjadi lebih mudah.

Dalam artikel ini, kami akan menerapkan contoh untuk menunjukkan ciri dan kemampuannya.

2. Persediaan

2.1. Ketergantungan Maven

Pertama, kita perlu menambahkan pergantungan commons-dbutil dan h2 ke pom.xml kami :

 commons-dbutils commons-dbutils 1.6   com.h2database h2 1.4.196 

Anda boleh mendapatkan versi terbaru dari commons-dbutils dan h2 di Maven Central.

2.2. Pangkalan Data Ujian

Dengan kebergantungan kita, mari buat skrip untuk membuat jadual dan rekod yang akan kita gunakan:

CREATE TABLE employee( id int NOT NULL PRIMARY KEY auto_increment, firstname varchar(255), lastname varchar(255), salary double, hireddate date, ); CREATE TABLE email( id int NOT NULL PRIMARY KEY auto_increment, employeeid int, address varchar(255) ); INSERT INTO employee (firstname,lastname,salary,hireddate) VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy')); // ... INSERT INTO email (employeeid,address) VALUES (1, '[email protected]'); // ...

Semua contoh contoh ujian dalam artikel ini akan menggunakan sambungan yang baru dibuat ke pangkalan data dalam memori H2:

public class DbUtilsUnitTest { private Connection connection; @Before public void setupDB() throws Exception { Class.forName("org.h2.Driver"); String db = "jdbc:h2:mem:;INIT=runscript from 'classpath:/employees.sql'"; connection = DriverManager.getConnection(db); } @After public void closeBD() { DbUtils.closeQuietly(connection); } // ... }

2.3. POJO

Akhirnya, kita memerlukan dua kelas sederhana:

public class Employee { private Integer id; private String firstName; private String lastName; private Double salary; private Date hiredDate; // standard constructors, getters, and setters } public class Email { private Integer id; private Integer employeeId; private String address; // standard constructors, getters, and setters }

3. Pengenalan

The DbUtils perpustakaan menyediakan yang QueryRunner kelas sebagai pintu masuk utama bagi kebanyakan fungsi yang ada.

Kelas ini berfungsi dengan menerima sambungan ke pangkalan data, pernyataan SQL yang akan dieksekusi, dan senarai parameter pilihan untuk memberikan nilai bagi pemegang tempat pertanyaan.

Seperti yang akan kita lihat kemudian, beberapa kaedah juga menerima pelaksanaan ResultSetHandler - yang bertanggungjawab untuk mengubah contoh ResultSet menjadi objek yang diharapkan oleh aplikasi kita.

Sudah tentu, perpustakaan sudah menyediakan beberapa pelaksanaan yang menangani transformasi yang paling umum, seperti senarai, peta, dan JavaBeans.

4. Meminta Data

Setelah mengetahui asasnya, kami sudah bersedia untuk menanyakan pangkalan data kami.

Mari kita mulakan dengan contoh ringkas mendapatkan semua rekod dalam pangkalan data sebagai senarai peta menggunakan MapListHandler :

@Test public void givenResultHandler_whenExecutingQuery_thenExpectedList() throws SQLException { MapListHandler beanListHandler = new MapListHandler(); QueryRunner runner = new QueryRunner(); List list = runner.query(connection, "SELECT * FROM employee", beanListHandler); assertEquals(list.size(), 5); assertEquals(list.get(0).get("firstname"), "John"); assertEquals(list.get(4).get("firstname"), "Christian"); }

Seterusnya, berikut adalah contoh menggunakan BeanListHandler untuk mengubah hasilnya menjadi contoh Pekerja :

@Test public void givenResultHandler_whenExecutingQuery_thenEmployeeList() throws SQLException { BeanListHandler beanListHandler = new BeanListHandler(Employee.class); QueryRunner runner = new QueryRunner(); List employeeList = runner.query(connection, "SELECT * FROM employee", beanListHandler); assertEquals(employeeList.size(), 5); assertEquals(employeeList.get(0).getFirstName(), "John"); assertEquals(employeeList.get(4).getFirstName(), "Christian"); }

Untuk pertanyaan yang mengembalikan satu nilai, kita boleh menggunakan ScalarHandler :

@Test public void givenResultHandler_whenExecutingQuery_thenExpectedScalar() throws SQLException { ScalarHandler scalarHandler = new ScalarHandler(); QueryRunner runner = new QueryRunner(); String query = "SELECT COUNT(*) FROM employee"; long count = runner.query(connection, query, scalarHandler); assertEquals(count, 5); }

Untuk mempelajari semua pelaksanaan ResultSerHandler , anda boleh merujuk kepada dokumentasi ResultSetHandler .

4.1. Pengendali Tersuai

Kita juga dapat membuat pengendali khusus untuk meneruskan kaedah QueryRunner apabila kita memerlukan lebih banyak kawalan tentang bagaimana hasilnya akan berubah menjadi objek.

Ini dapat dilakukan dengan melaksanakan antara muka ResultSetHandler atau memperluas salah satu pelaksanaan yang ada yang disediakan oleh perpustakaan.

Mari kita lihat bagaimana pendekatan kedua. Pertama, mari tambah medan lain ke kelas Pekerja kami :

public class Employee { private List emails; // ... }

Sekarang, mari buat kelas yang memperluas jenis BeanListHandler dan menetapkan senarai e-mel untuk setiap pekerja:

public class EmployeeHandler extends BeanListHandler { private Connection connection; public EmployeeHandler(Connection con) { super(Employee.class); this.connection = con; } @Override public List handle(ResultSet rs) throws SQLException { List employees = super.handle(rs); QueryRunner runner = new QueryRunner(); BeanListHandler handler = new BeanListHandler(Email.class); String query = "SELECT * FROM email WHERE employeeid = ?"; for (Employee employee : employees) { List emails = runner.query(connection, query, handler, employee.getId()); employee.setEmails(emails); } return employees; } }

Perhatikan bahawa kami mengharapkan objek Connection di konstruktor sehingga kami dapat menjalankan pertanyaan untuk mendapatkan e-mel.

Akhirnya, mari kita uji kod kami untuk melihat apakah semuanya berfungsi seperti yang diharapkan:

@Test public void givenResultHandler_whenExecutingQuery_thenEmailsSetted() throws SQLException { EmployeeHandler employeeHandler = new EmployeeHandler(connection); QueryRunner runner = new QueryRunner(); List employees = runner.query(connection, "SELECT * FROM employee", employeeHandler); assertEquals(employees.get(0).getEmails().size(), 2); assertEquals(employees.get(2).getEmails().size(), 3); }

4.2. Pemproses Baris Tersuai

Dalam contoh kami, nama lajur jadual pekerja sepadan dengan nama medan kelas Karyawan kami (padanannya tidak peka huruf besar kecil). Namun, itu tidak selalu berlaku - misalnya apabila nama lajur menggunakan garis bawah untuk memisahkan kata majmuk.

Dalam situasi seperti ini, kita dapat memanfaatkan antara muka RowProcessor dan pelaksanaannya untuk memetakan nama lajur ke bidang yang sesuai di kelas kita.

Mari lihat bagaimana rupa ini. Pertama, mari buat jadual lain dan masukkan beberapa rekod ke dalamnya:

CREATE TABLE employee_legacy ( id int NOT NULL PRIMARY KEY auto_increment, first_name varchar(255), last_name varchar(255), salary double, hired_date date, ); INSERT INTO employee_legacy (first_name,last_name,salary,hired_date) VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy')); // ...

Sekarang, mari ubah suai kelas EmployeeHandler kami :

public class EmployeeHandler extends BeanListHandler { // ... public EmployeeHandler(Connection con) { super(Employee.class, new BasicRowProcessor(new BeanProcessor(getColumnsToFieldsMap()))); // ... } public static Map getColumnsToFieldsMap() { Map columnsToFieldsMap = new HashMap(); columnsToFieldsMap.put("FIRST_NAME", "firstName"); columnsToFieldsMap.put("LAST_NAME", "lastName"); columnsToFieldsMap.put("HIRED_DATE", "hiredDate"); return columnsToFieldsMap; } // ... }

Perhatikan bahawa kita menggunakan BeanProcessor untuk melakukan pemetaan lajur yang sebenarnya ke bidang dan hanya untuk yang perlu ditangani.

Finally, let's test everything is ok:

@Test public void givenResultHandler_whenExecutingQuery_thenAllPropertiesSetted() throws SQLException { EmployeeHandler employeeHandler = new EmployeeHandler(connection); QueryRunner runner = new QueryRunner(); String query = "SELECT * FROM employee_legacy"; List employees = runner.query(connection, query, employeeHandler); assertEquals((int) employees.get(0).getId(), 1); assertEquals(employees.get(0).getFirstName(), "John"); }

5. Inserting Records

The QueryRunner class provides two approaches to creating records in a database.

The first one is to use the update() method and pass the SQL statement and an optional list of replacement parameters. The method returns the number of inserted records:

@Test public void whenInserting_thenInserted() throws SQLException { QueryRunner runner = new QueryRunner(); String insertSQL = "INSERT INTO employee (firstname,lastname,salary, hireddate) " + "VALUES (?, ?, ?, ?)"; int numRowsInserted = runner.update( connection, insertSQL, "Leia", "Kane", 60000.60, new Date()); assertEquals(numRowsInserted, 1); }

The second one is to use the insert() method that, in addition to the SQL statement and replacement parameters, needs a ResultSetHandler to transform the resulting auto-generated keys. The return value will be what the handler returns:

@Test public void givenHandler_whenInserting_thenExpectedId() throws SQLException { ScalarHandler scalarHandler = new ScalarHandler(); QueryRunner runner = new QueryRunner(); String insertSQL = "INSERT INTO employee (firstname,lastname,salary, hireddate) " + "VALUES (?, ?, ?, ?)"; int newId = runner.insert( connection, insertSQL, scalarHandler, "Jenny", "Medici", 60000.60, new Date()); assertEquals(newId, 6); }

6. Updating and Deleting

The update() method of the QueryRunner class can also be used to modify and erase records from our database.

Its usage is trivial. Here's an example of how to update an employee's salary:

@Test public void givenSalary_whenUpdating_thenUpdated() throws SQLException { double salary = 35000; QueryRunner runner = new QueryRunner(); String updateSQL = "UPDATE employee SET salary = salary * 1.1 WHERE salary <= ?"; int numRowsUpdated = runner.update(connection, updateSQL, salary); assertEquals(numRowsUpdated, 3); }

And here's another to delete an employee with the given id:

@Test public void whenDeletingRecord_thenDeleted() throws SQLException { QueryRunner runner = new QueryRunner(); String deleteSQL = "DELETE FROM employee WHERE id = ?"; int numRowsDeleted = runner.update(connection, deleteSQL, 3); assertEquals(numRowsDeleted, 1); }

7. Asynchronous Operations

DbUtils provides the AsyncQueryRunner class to execute operations asynchronously. The methods on this class have a correspondence with those of QueryRunner class, except that they return a Future instance.

Here's an example to obtain all employees in the database, waiting up to 10 seconds to get the results:

@Test public void givenAsyncRunner_whenExecutingQuery_thenExpectedList() throws Exception { AsyncQueryRunner runner = new AsyncQueryRunner(Executors.newCachedThreadPool()); EmployeeHandler employeeHandler = new EmployeeHandler(connection); String query = "SELECT * FROM employee"; Future
    
      future = runner.query(connection, query, employeeHandler); List employeeList = future.get(10, TimeUnit.SECONDS); assertEquals(employeeList.size(), 5); }
    

8. Conclusion

In this tutorial, we explored the most notable features of the Apache Commons DbUtils library.

Kami menyoal data dan mengubahnya menjadi jenis objek yang berbeza, memasukkan catatan mendapatkan kunci utama yang dihasilkan dan mengemas kini dan menghapus data berdasarkan kriteria yang diberikan. Kami juga memanfaatkan kelas AsyncQueryRunner untuk melaksanakan operasi pertanyaan secara asinkron.

Dan, seperti biasa, kod sumber lengkap untuk artikel ini boleh didapati di Github.