Wednesday, June 24, 2015

EasyJDBC: using JDBC in Hibernate style

1. Intro 

This post about just one file EasyJDBC.java which I developed just for checking: is it possible to create lightweight alternative to Hibernate or not.  

Of course, Hibernate framework is very popular, and it's hard to find complex application which is working with databases and not using Hibernate. It gives to developer a lot of tools "out of the box" like lazy-initialisation, working with one-to-many/many-to-many relations, caching and many others.  It's like a "space-ship" which can do do almost everything, even to reach the starts.

But sometimes in case of  small simple application, I was thinking like: "why am I using hibernate? In that project I'm using less than 1% of it functionality!". For some simple applications  I don't need a "space ship" - for them "bike" will be well enough! So decided to think:  how can I avoid using that tool which takes so much megabytes in war file? The first answer was, of course, JDBC. But everyone who used JDBC, was dreaming about something like Hibernate with easy mapping of class fields to table columns using annotations, easy mapping to java objects all database operations :) Because creating queries, taking results from result set in JDBC are taking a lot of time in terms of coding.

So, after all, I decided to create simple utility which can work with persistence annotations and can map results of sql queries directly to java objects. It's just a annotation parser+sql builder - nothing more. It has no support of one-to-many/many-to-many relations, and support only few data types : Long, BigDecimal, String, Date. But for some easy projects that can be enough.

This utility it's just a one file EasyJDBC.java - so it's very easy to include it to project as source code without adding megabytes of .jar files which require Hibernate. 

2.  Features

So let's describe what this utility can do:

- Support of java persistence annotations.
@Entity
@Table(name="DEPARTMENT")
public class Department {

    @Id
    @Column(name = "department_id")
    private Long departmentId;

    @Column(name = "department_name")
    private String departmentName; 
 
As you can see: just like in case of using Hibernate. Class Department will be mapped with DEPARTMENT table in database.

- Mapping query results to java classes.  
On creating DAO we have to define result mapping class  :


EasyJDBC<Department> depDao=new EasyJDBC<Department>(Department.class);

Now using this DAO we will get/put objects of Department class from/into database.

- Hibernate-style operations.
Supported main hibernate operations like save, update, delete  

Department dep1=createDepartment("First");
depDao.save(dep1);

- Mapping query results to objects.
All queries are returning List<> of object defined on DAO creation:

List<Department> deps=depDao.getAll();

- Internal query-builder to simplify creation of database queries.
There are several options to simplify creation of sql query :


        List<Department> deps=depDao
                .getQueryBuilder()
                .addFieldCondition(Department.COLUMNS.DEPARTMENT_NAME.name(), "=", "First")
                .executeSelect();

3. Example of usage: pom.xml and test-project structure

Project structure is very simple: in "main" folder I left only utility itself and all stuff related with demonstration are in "test" folder:


pom.xml is also very simple: only dependencies for java persistence annotations and for H2 DB which used for tests:


<?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.demien.easy</groupId>
    <artifactId>jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>

   <dependencies>
    <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0</version>
    </dependency>

       <dependency>
           <groupId>com.h2database</groupId>
           <artifactId>h2</artifactId>
           <version>1.4.178</version>
       </dependency>

   </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>

                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>


4. Example of usage: annotated DTO objects.

Here - only "top" of files: without getters/setters, full text can be downloaded from the link at the page bottom.

User.java:

package com.demien.easy.dto;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Table(name="USER")
public class User {

    public enum COLUMNS{USER_ID, USER_NAME, EMP_DATE, SALARY, DEPARTMENT_ID}

    @Id
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "emp_date")
    private Date employmentDate;

    @Column(name = "salary")
    private BigDecimal salary;

    @Column(name = "department_id")
    private Long departmentId;

    public User() {
    }

Department.java:

package com.demien.easy.dto;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * Created by kovalskd on 5/7/2015.
 */
@Entity
@Table(name="DEPARTMENT")
public class Department {

    public enum COLUMNS{DEPARTMENT_ID, DEPARTMENT_NAME}

    @Id
    @Column(name = "department_id")
    private Long departmentId;

    @Column(name = "department_name")
    private String departmentName;

    public Department() {}


5. Example of usage: test application.

In listing below - example of usage of this utility. Every step is well commented. 


package com.demien.easy;

import com.demien.easy.dto.Department;
import com.demien.easy.dto.User;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * Created by kovalskd on 5/21/2015.
 */
public class EasyJDBCTest {

    static Connection connection;
    static EasyJDBC<User> userDao=new EasyJDBC<User>(User.class);
    static EasyJDBC<Department> depDao=new EasyJDBC<Department>(Department.class);


    public static void init() throws ClassNotFoundException, SQLException {
        Class.forName("org.h2.Driver");
        connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");
        userDao.setConnection(connection);
        userDao.createTable();
        depDao.setConnection(connection);
        depDao.createTable();
    }

    public static Department createDepartment(String name) {
        Department dep=new Department();
        Long id=(Long)depDao.getMaxId() + 1;
        dep.setDepartmentId(id);
        dep.setDepartmentName(name);
        return dep;
    }

    public static User createUser(String name, BigDecimal salary, Long departmentId) {
        User user=new User();
        Long id=(Long)userDao.getMaxId() + 1;
        user.setUserId(id);
        user.setUserName(name);
        user.setEmploymentDate(new Date());
        user.setSalary(salary);
        user.setDepartmentId(departmentId);
        return user;

    }

    public static void main(String args[]) throws Exception {
        init();
        Department dep1=createDepartment("First");
        depDao.save(dep1);

        User user1=createUser("Huan Sebastyan", new BigDecimal(123.23), dep1.getDepartmentId());
        userDao.save(user1);

        User user2=createUser("Joe Black", new BigDecimal(333.33), dep1.getDepartmentId());
        userDao.save(user2);

        Department dep2=createDepartment("Second");
        depDao.save(dep2);

        User user3=createUser("Developer", new BigDecimal(100), dep2.getDepartmentId());
        userDao.save(user3);

        User user4=createUser("Just Another User", new BigDecimal(500), dep2.getDepartmentId());
        userDao.save(user4);

        User user5= createUser("new user", new BigDecimal(666.66), dep2.getDepartmentId());
        userDao.save(user5);

        List<Department> deps=depDao.getAll();
        System.out.println("DEPARTMENTS:"+Arrays.toString(deps.toArray()));
        List<User> users=userDao.getAll();
        System.out.println("USERS:"+Arrays.toString(users.toArray()));

        // get by id test
        System.out.println("\n get by id test: userDao.getById(1L)");
        User user=userDao.getById(1L);
        System.out.println(user);

        // query test
        System.out.println("\n query test: User.COLUMNS.USER_NAME.name() in ('Huan Sebastyan','Joe Black')");
        users= userDao.getQueryBuilder().addWhereCondition(User.COLUMNS.USER_NAME.name()+" in ('Huan Sebastyan','Joe Black')").executeSelect();
        System.out.println(Arrays.toString(users.toArray()));

        // addFieldEqualsCondition test
        System.out.println("\n addFieldCondition test: (User.COLUMNS.USER_ID.name(), <=, 2L)");
        users= userDao.getQueryBuilder().addFieldCondition(User.COLUMNS.USER_ID.name(), "<=", 2L).executeSelect();
        System.out.println(Arrays.toString(users.toArray()));

        // addFieldEqualsCondition test
        System.out.println("\n addFieldEqualsCondition test: (User.COLUMNS.USER_NAME.name(), Developer)");
        users= userDao.getQueryBuilder().addFieldEqualsCondition(User.COLUMNS.USER_NAME.name(), "Developer").executeSelect();
        System.out.println(Arrays.toString(users.toArray()));



        // update test
        System.out.println("\n update test:");
        user=userDao.getById(1L);
        user.setUserName("Updated name");
        userDao.update(user);
        user=userDao.getById(1L);
        System.out.println(user);

        // delete test
        System.out.println("\n delete test - deleted user1,user2,user3:");
        userDao.delete(user1);
        userDao.delete(user2);
        userDao.delete(user3);
        users = userDao.getAll();
        System.out.println(Arrays.toString(users.toArray()));

    }
}


6. Example of usage: results

The results itself:

DEPARTMENTS:[Department{departmentId=1, departmentName='First'}, Department{departmentId=2, departmentName='Second'}]
USERS:[User{userId=1, userName='Huan Sebastyan', employmentDate='2015-06-24', salary='123.23', departmentId='1'}, User{userId=2, userName='Joe Black', employmentDate='2015-06-24', salary='333.33', departmentId='1'}, User{userId=3, userName='Developer', employmentDate='2015-06-24', salary='100.00', departmentId='2'}, User{userId=4, userName='Just Another User', employmentDate='2015-06-24', salary='500.00', departmentId='2'}, User{userId=5, userName='new user', employmentDate='2015-06-24', salary='666.66', departmentId='2'}]

 get by id test: userDao.getById(1L)
User{userId=1, userName='Huan Sebastyan', employmentDate='2015-06-24', salary='123.23', departmentId='1'}

 query test: User.COLUMNS.USER_NAME.name() in ('Huan Sebastyan','Joe Black')
[User{userId=1, userName='Huan Sebastyan', employmentDate='2015-06-24', salary='123.23', departmentId='1'}, User{userId=2, userName='Joe Black', employmentDate='2015-06-24', salary='333.33', departmentId='1'}]

 addFieldCondition test: (User.COLUMNS.USER_ID.name(), <=, 2L)
[User{userId=1, userName='Huan Sebastyan', employmentDate='2015-06-24', salary='123.23', departmentId='1'}, User{userId=2, userName='Joe Black', employmentDate='2015-06-24', salary='333.33', departmentId='1'}]

 addFieldEqualsCondition test: (User.COLUMNS.USER_NAME.name(), Developer)
[User{userId=3, userName='Developer', employmentDate='2015-06-24', salary='100.00', departmentId='2'}]

 update test:
User{userId=1, userName='Updated name', employmentDate='2015-06-24', salary='123.23', departmentId='1'}

 delete test - deleted user1,user2,user3:
[User{userId=4, userName='Just Another User', employmentDate='2015-06-24', salary='500.00', departmentId='2'}, User{userId=5, userName='new user', employmentDate='2015-06-24', salary='666.66', departmentId='2'}]

Process finished with exit code 0

 

7. The end. 

Of course, it's not a "enterprise-level" library like Hibernate. But may be it will be useful for somebody. Full code can be get from git repository :
https://github.com/demien666/easy_repository.git