Dalam membuat sebuah sistem aplikasi pasti tidak terlepas dari yang namanya operasi menyimpan atau membaca sebuah data, baik data yang tersimpan dalam sebuah RDBMS, noSQL, ataupun sebuah teks file. Dalam Java operasi menyimpan atau membaca data kedalam sebuah RDBMS terdapat banyak cara, salah satunya adalah dengan JDBC standar yang telah disediakan oleh Java sendiri.

Database Driver

Sebelum dapat menjalankan operasi Database kita terlebih dahulu perlu mencari Driver Database yang akan digunakan, sehingga perlu diperhatikan RDBMS apa yang akan digunakan. Sebagai contoh untuk MariaDB dapat menggunakan MySQL Connector, sebetulnya terdapat MariaDB Connector namun hingga tulisan ini dimuat masih terdapat beberapa bug dan setelah mencari beberapa referensi memang disarankan untuk menggunakan MySQL Connector.

Library Database Driver ini harus kita ikut sertakan dalam project kita pada saat dibuild, pastikan Build Path untuk mengarah ke library Database Driver ini sudah benar. Dalam tutorial ini untuk mempermudah penanganan dependency menggunakan Maven, sehingga kita cukup menyertakan dalam pom.xml seperti berikut

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.40</version>
    <scope>provided</scope>
</dependency>

DriverManager vs DataSource

Setelah menambahkan Database Driver langkah selanjutnya adalah membuat source code untuk membangun koneksi dengan RDBMS, disini terdapat dua cara yakni menggunakan DriverManager atau DataSource. Namun DriverManager adalah cara yang cukup lama, sehingga disarankan untuk menggunakan DataSource. Beberapa kekurangan ketika menggunakan DriverManager dari pengalaman saya adalah

  • Sulitnya menggunakan metode Dependency Injection.
  • Sulitnya memanage buka tutup koneksi terhadap Database.

DataSource dalam Java sendiri merupakan sebuah Interface, sehingga untuk implementasinya tergantung dengan Database yang akan digunakan. Dalam hal ini untuk MariaDB dapat menggunakan com.mysql.jdbc.jdbc2.optional.MysqlDataSource(), namun hal ini juga kurang disarankan karena sistem kita nantinya akan sangat tightly couple terhadap vendor Database. Sehingga sangat disarankan untuk menggunakan Library Connection Polling yang dapat mengabstraksi DataSource dari tiap database. Dalam tutorial ini menggunakan Apache DBCP sehingga dalam file pom.xml kita perlu mendefinisikan apache dbcp tersebut

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
    <scope>provided</scope>
</dependency> 

Database Configuration Class

Selanjutnya yang menurut saya cukup penting adalah membuat sebuah class yang berfungsi untuk membuat koneksi terhadap RDBMS yang nantinya akan diinject kedalam class yang berfungsi untuk melakukan operasi ke RDBMS. Kemudian kenapa menggunakan Dependency Injection? hal tersebut sudah dijelaskan secara lengkap dan jelas oleh Endy.

Secara singkat Dependency Injection memungkinkan untuk membuat sebuah object yang dalam hal ini adalah DataSource bagi semua class yang nantinya membutuhkan object tersebut. Apabila tanpa Dependency Injection maka setiap class yang akan mengakses kedalam database harus membuat object DataSource itu sendiri seperti berikut

public class BookDao {
    private DataSource dataSource;

    public BookDao() {
        dataSource = new BasicDataSource();
        dataSource.setDriverClassName(DATABASE_DRIVER);
        dataSource.setUrl(DATABASE_URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD); 
    }
}

Sekarang bayangkan apabila kita memiliki puluhan atau bahkan ratusan class yang berfungsi untuk mengakses RDBMS maka pembuatan object DataSource akan tersebar kemana-mana, sehingga apabila dikemudian hari ingin mengganti database maka akan sangat merepotkan karena harus menemukan semua class yang menggunakan object DataSource. Namun perlu diperhatikan juga dalam membuat object yang berhubungan dengan RDBMS karena akan mempengaruhi performa dari sistem kita.

Oleh karena itu salah satu solusinya adalah menerapkan Singleton Pattern pada class yang akan mengkonfigurasi DataSource, hal ini telah cukup dibahas pada Stackoverflow mengenai mengapa penggunaan class yang berhubungan dengan RDBMS perlu diperhatikan dengan baik. Berikut adalah contoh class untuk configurasi koneksi terhadap database.

public class DatabaseConfig {
    private static DatabaseConfig instance;
    private static final String DATABASE_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/demo";
    private static final String USERNAME = "demo";
    private static final String PASSWORD = "demo";
    // init DataSource object
    private static BasicDataSource dataSource;

    // Private Constructor in order use Singleton Pattern
    private DatabaseConfig() {
    }

    static {
        dataSource = new BasicDataSource();
        dataSource.setDriverClassName(DATABASE_DRIVER);
        dataSource.setUrl(DATABASE_URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
        try {
            dataSource.getConnection();
        } catch (SQLException e) {
        }
    }

    public static DatabaseConfig getInstance() {
        if (instance == null) {
            instance = new DatabaseConfig();
        }
        return instance;
    }

    public BasicDataSource getDataSource() {
        return dataSource;
    }
} 

Entity Class

Entity class merupakan sebuah class POJO sederhana yang nantinya berfungsi untuk mapping hasil query database, penjelasan yang cukup lengkap dapat dibaca disini namun sebagai rangkuman dengan menggunakan object dalam memappingkan data hasil query dari database akan lebih mudah dibaca dibandingkan menggunakan arrya atau map.

Sebagai contoh apabila menggunakan array:

String book = new String[3];
book[index] <-- tidak dapat memanfaatkan fitur autocomplete dari IDE karena kita harus mendefinisikan sendiri

Menggunakan class object:

Book book = new Book();
book.getTitle(); <-- dapat memanfaatkan fitur autocomplete IDE, lebih mudah dibaca serta menghindari kesalahan

DAO Layer

Data Access Object DAO merupakan Design Pattern pada JEE yakni mengumpulkan operasi database dalam layer DAO. Sehingga class class yang terdapat pada layer DAO memiliki fungsi untuk melakukan operasi ke database seperti insert, udpate serta delete. Berikut contoh class DAO:

public class AuthorDaoImpl extends BaseDao<Author> implements BasicAction<Author, Integer> {
    // logger
    private static final Logger LOGGER = Logger.getLogger(AuthorDaoImpl.class.getName());
    private DataSource dataSource;
    private Connection connection;
    // IMPORTANT: give spaces between SELECT, FROM, and WHERE
    private static final String INSERT = "INSERT INTO author(name) VALUES(?)";
    private static final String GET_ALL = "SELECT * FROM author LIMIT ?, ?";

    /**
     * Constructor injection
     *
     * @param dataSource
     */
    public AuthorDaoImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void create(Author entity) {
        LOGGER.log(Level.FINER, "Perform Create Data into Database");

        try {
            this.connection = this.dataSource.getConnection();
            this.connection.setAutoCommit(false);

            PreparedStatement ps = this.connection.prepareStatement(INSERT);

            // index number is same with order of ? in query statements
            ps.setString(1, entity.getName());

            ps.executeUpdate();

            this.connection.commit();
        } catch (Exception err) {
            LOGGER.log(Level.WARNING, "Insert Data Error: {0}",
                    new Object[]{err.getMessage()});
            try {
                LOGGER.log(Level.FINE, "Insert Data Failed! Rollback Everything");
                this.connection.rollback();
            } catch (SQLException e) {
                LOGGER.log(Level.SEVERE, e.getMessage());
            }
        } finally {
            LOGGER.log(Level.FINE, "Insert Data Done");
            try {
                LOGGER.log(Level.FINE, "Close Connection");
                this.connection.setAutoCommit(true);
                this.connection.close(); // since using ConnectionPolling it will just in SLEEP Mode
            } catch (SQLException e) {
                LOGGER.log(Level.SEVERE, e.getMessage());
            }
        }
    }

    @Override
    public List<Author> findAll(Integer currentPage, Integer resultRow) {
        LOGGER.log(Level.FINER, "Perform Get All Data in Database");
        List<Author> authors = new ArrayList<Author>();
        Integer limit = paginationLimit(currentPage, resultRow);
        try {
            this.connection = this.dataSource.getConnection();
            PreparedStatement ps = this.connection.prepareStatement(GET_ALL);

            // index number is same with order of ? in query statements
            ps.setInt(1, limit);
            ps.setInt(2, resultRow);

            ResultSet rs = ps.executeQuery();

            LOGGER.log(Level.FINE, "Start Convert ResultSet into Object");
            while (rs.next()) {
                Author author = convert(rs);
                authors.add(author);
            }

        } catch (SQLException err) {
            LOGGER.log(Level.WARNING, "Get All Data Error: {0}",
                    new Object[]{err.getMessage()});
        } finally {
            LOGGER.log(Level.FINE, "Get All Data Done");
            try {
                LOGGER.log(Level.FINE, "Close Connection");
                this.connection.close(); // since using ConnectionPolling it will just in SLEEP Mode
            } catch (SQLException e) {
                LOGGER.log(Level.SEVERE, e.getMessage());
            }
        }
        return authors;
    } 
}

Kesimpulan

Dalam proses menggunakan fitur JDBC dari Java ini saya mempelajari diantaranya:

  • Hindari penggunaan DriverManager, lebih baik menggunakan DataSource.
  • Manfaatkan Dependency Injection agar lebih mudah dalam memaintenance source code.
  • Terapkan Singletone Design Pattern terhadap class database configuration, mengingat besarnya usaha dalam membuat object baru untuk operasi database.
  • Pergunakan DAO pattern agar source code kita lebih terorganisir.

Untuk source code dapat dilihat pada github