[JAVA] JDBC connection mapper that eats SQL and returns beans

Introduction

There are many Java sources out there that have no choice but to realize legacy JDBC connections. It's one of the programmer's worries. If it's just maintenance, you may have to make a new one.

As a case that comes to mind immediately in the sense of legacy It is in the form of "mapping the information pulled from DB by Select as a bean" The processing process is ** still cool ** slack ...

"Keep query results once in ResultSet" "Turn the cursor of ResultSet to fill the bean" "Implement business logic by looping beans" It's like that.

The design and definition of SQL and Bean are correct, but in the process It is often the case that the whole thing gets stuck just by making a careless mistake.

More intuitively, ** Isn't it complete with just SQL and Bean? ** This is the article.

What you want to achieve

As the title says. Strictly speaking, the "returned bean type" must be specified in advance. Is it close to ** My Bottle of Starbucks **? When you place an order (SQL) and give my bottle (Bean) to the clerk My bottle is filled with coffee and returns.

What was thrown away in realization

Type safety

You can see it by looking at the code that comes out after this. There is a part that ** completely ignores ** "type safety", which is one of the major features of Java. Personally, it's "separate", but people who get caught may get caught. Please forgive me.

Deep digging of existing ORM technology

[ORM](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83 % 88% E9% 96% A2% E4% BF% 82% E3% 83% 9E% E3% 83% 83% E3% 83% 94% E3% 83% B3% E3% 82% B0) is not detailed at all. So with better functionality with existing middleware and libraries There are likely to be resources that do what I want to do, There is also a closeout that there are many negative opinions rather than pros and cons of ORM itself. Although it is premature, I have stopped forcibly thrusting my neck. (Personally, ORM recognizes it as a ** broad ** word, so I don't think it's all bad It is a fact that I hate word hunting, so I avoided it.)

Articles that I used as a reference

The following article is the closest to what I want to do. The source of the base part is diverted almost as it is. [Java] Let's make a DB access library!

Java version

Java 6 or later

Character

JDBCManager class

DB connection infrastructure class and class that controls mapping.

SqlParameter class

A class that manages placeholders that are parameters to SQL. This is a ** type danger ** that is not type safe.

SQL (query string)

The SQL statement you want to execute. Regardless of CRUD, there is no need to make it persistent as a file or DB record.

Bean class according to Select

Bean class corresponding to the extraction column of Select. Create one for each Select statement.

I/O The I / O required for the minimum operation is as follows.

input

-Full path of JDBC driver class (oracle.jdbc.driver.OracleDriver, etc.) -JDBC connector (jdbc: oracle: thin: @ // localhost: 1521 / xe, etc.) -JDBC connection user -JDBC connection password ・ SQL -SQL parameters (however, not required if placeholders are not used)

Output

Bean

Base source

It is based on the JDBCManager and SqlParameter classes.

JDBCManager class

JDBCManager class


import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class JDBCManager {
    private Connection conn;

    @SuppressWarnings("unused")
    private JDBCManager() throws Throwable{}

    public JDBCManager(String driver,String url,String user,String password) throws ClassNotFoundException,SQLException{
        Class.forName(driver);
        this.conn = DriverManager.getConnection(url, user, password);
        this.conn.setAutoCommit(false);
    }

    private void setParams(PreparedStatement statement, SqlParameter sqlParameter) throws SQLException {
    	if(sqlParameter == null || !sqlParameter.hasParameters()) {
    		return;
    	}
        int paramNo = 1;
        for( Object param : sqlParameter.toParameter() ) {
            statement.setObject(paramNo++, param);
        }
    }

    public void commit() throws SQLException{
        this.conn.commit();
    }

    public void rollback() throws SQLException{
        this.conn.rollback();
    }

    public void close() throws SQLException{
        this.conn.close();
    }

    private <E> E toObject(ResultSet rs, Class<E> clazz) throws InstantiationException, IllegalAccessException, SQLException {
        E bean = null;
        if( rs.next() ) {
            Field[] fields = clazz.getDeclaredFields();
            bean = clazz.newInstance();
            for (Field f : fields) {
                f.setAccessible(true);
                Object val = rs.getObject(f.getName());
                f.set(bean, val);
            }
        }
        return bean;
    }

    private <E> List<E> toObjectList(ResultSet rs, Class<E> clazz) throws SQLException, InstantiationException, IllegalAccessException  {
        List<E> rsList = new ArrayList<E>();
        while (rs.next()) {
            Field[] fields = clazz.getDeclaredFields();
            E bean = clazz.newInstance();
            for( Field f: fields ) {
                f.setAccessible(true);
                Object val = rs.getObject( f.getName() );
                f.set(bean, val);
            }
            rsList.add(bean);
        }
        return rsList;
    }

    private SQLException sqlErr(Throwable e, CharSequence sql) {
        return new SQLException("sql error!\nerror occurred sql="+sql, e);
    }


    public int insert(String sql, SqlParameter sqlParameter) throws SQLException {
    	return exert(sql,sqlParameter);
    }

    public int update(String sql, SqlParameter sqlParameter) throws SQLException {
    	return exert(sql,sqlParameter);
    }

    public int delete(String sql, SqlParameter sqlParameter) throws SQLException {
    	return exert(sql,sqlParameter);
    }

    private int exert(String sql, SqlParameter sqlParameter) throws SQLException {
        PreparedStatement statement = null;
        try {
            statement = conn.prepareStatement(sql.toString());
            setParams(statement,sqlParameter);
            int ret = statement.executeUpdate();
            return ret;
        }catch (SQLException e){
            throw sqlErr(e, sql);
        }finally {
            try {
                if(statement != null){
                    statement.close();
                }
            } catch (SQLException e) {
            	e.printStackTrace();
            }
        }
    }

    public <E> E selectOne(String sql, Class<E> clazz) throws SQLException {
    	return selectOne(sql,clazz,null);
    }

    public <E> E selectOne(String sql, Class<E> clazz, SqlParameter sqlParameter) throws SQLException {
        ResultSet rs = null;
        PreparedStatement statement = null;
        try{
            statement = conn.prepareStatement(sql.toString());
            setParams(statement,sqlParameter);
            rs = statement.executeQuery();

            E val = toObject(rs, clazz);
            return val;
        }catch(Exception e) {
            throw sqlErr(e, sql);
        }finally {
            try {
                if( rs != null ) {
                    rs.close();
                }
            }catch(SQLException e) {
            	e.printStackTrace();
            }finally {
            	try {
                    if(statement != null){
                        statement.close();
                    }
            	}catch(SQLException e) {
            		e.printStackTrace();
            	}
            }
        }
    }

    public <E> List<E> selectList(String sql, Class<E> clazz) throws SQLException {
    	return selectList(sql,clazz,null);
    }

    public <E> List<E> selectList(String sql, Class<E> clazz, SqlParameter sqlParameter) throws SQLException {
        ResultSet rs = null;
        PreparedStatement statement = null;
        try {
            statement = conn.prepareStatement(sql.toString());
            setParams(statement,sqlParameter);
            rs = statement.executeQuery();
            List<E> rsList = toObjectList(rs, clazz);
            return rsList;
        }catch(Exception e) {
            throw sqlErr(e, sql);
        }finally {
            try {
                if( rs != null ) {
                    rs.close();
                }
            }catch(SQLException e) {
            	e.printStackTrace();
            }finally {
            	try {
                    if(statement != null){
                        statement.close();
                    }
            	}catch(SQLException e) {
            		e.printStackTrace();
            	}
            }
        }
    }
}

SqlParameter class

SqlParameter class


import java.util.ArrayList;
import java.util.List;

public class SqlParameter {
    @SuppressWarnings("rawtypes")
    private List list = null;

    @SuppressWarnings("rawtypes")
    public SqlParameter(int paramNumber){
        list = new ArrayList(paramNumber);
    }

    @SuppressWarnings("rawtypes")
	public SqlParameter(){
    	list = new ArrayList();
    }

    @SuppressWarnings("unchecked")
    public <E> void addParameter(E e){
        list.add(e);
    }

    public boolean hasParameters() {
    	return list.size()==0 ? false : true;
    }

    public Object[] toParameter(){
        return list.toArray();
    }
}

Operation sample

I will move it in four cases. The target DB is "Oracle (Express Edition)". The operation targets are the "EMP" and "DEPT" tables that exist by default in the schema.

(1) Select: Multiple acquisition SQL without WHERE clause (no placeholder required) (2) Select: Multiple acquisition SQL with WHERE clause (with placeholder) ③ Select: Single acquisition SQL ④ Update (insert, update, delete)

We will create beans according to each SQL. The point is to make the Select alias (AS) and the Bean field name the same **.

Also, declare the bean field type in the class according to the JDBC specification.

In Select of this sample, we are operating on a single table. The same processing can be performed with Select for multiple tables using ** join **.

The point is "** SQL and Bean should match **".

(1) Select: Multiple acquisition SQL without WHERE clause (no placeholder required)

Select_Multiple acquisition SQL without WHERE clause (no placeholder required)


SELECT 
	--VARCHAR2
	DNAME AS dname 
FROM 
	DEPT

DeptBean class


public class DeptBean {
	private String dname = null;

	public String getDname() {
		return dname;
	}

	public void setDname(String dname) {
		this.dname = dname;
	}
}

(2) Select: Get multiple records with WHERE clause (with placeholder) SQL

Select_Get multiple records with WHERE clause (with placeholder) SQL


SELECT 
	 --NUMBER
	 EMPNO AS empNo, 
	 --VARCHAR2
	 ENAME AS ename, 
	 --DATE
	 HIREDATE AS hireDate 
 FROM 
	 EMP 
 WHERE 
	 DEPTNO = ? 
	 AND 
	 JOB = ? 

EmpBean class


import java.math.BigDecimal;
import java.sql.Timestamp;

public class EmpBean {
	private BigDecimal empNo = null;
	private String ename = null;
	private Timestamp hireDate = null;

	public BigDecimal getEmpNo() {
		return empNo;
	}
	public void setEmpNo(BigDecimal empNo) {
		this.empNo = empNo;
	}
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public Timestamp getHireDate() {
		return hireDate;
	}
	public void setHireDate(Timestamp hireDate) {
		this.hireDate = hireDate;
	}
}

③ Select: Single acquisition SQL

Select_Single capture SQL


SELECT 
	 --NUMBER
	 EMPNO AS empNo, 
	 --VARCHAR2
	 ENAME AS ename, 
	 --DATE
	 HIREDATE AS hireDate 
 FROM 
	 EMP 
 WHERE 
	//PK
	 EMPNO = ?

EmpBean class


(Since it is the same as above, it is omitted)

④ Update (insert, update, delete)

Let's use insert as a sample. Since the result is received as an int, you do not need to create a bean for updating.

insert


INSERT INTO DEPT 
	 --NUMBER,VARCHAR2,VARCHAR2
	 (DEPTNO, DNAME, LOC) 
	 values 
	 (?, ?, ?) 

Test class

I will move all the above four cases.

Test class


import java.math.BigDecimal;
import java.util.List;

public class JDBCTest {
	public static void main(String[] args) throws Throwable{
		JDBCTest t = new JDBCTest();
		t.execute();
	}

	public void execute() throws Throwable{
		//JDBC connection information
		String driver = "oracle.jdbc.driver.OracleDriver";
		String url = "jdbc:oracle:thin:@//localhost:1521/xe";
		String user = "ouser";
		String password = "ouser";
		//Initialize JDBC Manager
		JDBCManager jdbc = new JDBCManager(driver,url,user,password);

		//Pattern 1. Multiple acquisition SQL without WHERE clause (no placeholder required)
		String sql = "SELECT "
						+ "DNAME AS dname "
					+ "FROM "
						+ "DEPT ";
		//Get List
		List<DeptBean> dlist = jdbc.selectList(sql, DeptBean.class);

		System.out.println("――― Pattern 1 ―――");
		for(DeptBean d : dlist) {
			System.out.println(d.getDname());
		}

		//Pattern 2. Get multiple records with WHERE clause (with placeholder) SQL
		//Fit to bean field with alias
		sql =		"SELECT "
						+ "EMPNO AS empNo, "
						+ "ENAME AS ename, "
						+ "HIREDATE AS hireDate "
					+ "FROM "
						+ "EMP "
					+ "WHERE "
						+ "DEPTNO = ? "
						+ "AND "
						+ "JOB = ? ";

		//Two placeholders
		SqlParameter p = new SqlParameter(2);
		//Bind in order from the beginning
		p.addParameter(new BigDecimal(30));
		p.addParameter("SALESMAN");

		//Get List
		List<EmpBean> elist = jdbc.selectList(sql, EmpBean.class, p);

		System.out.println("――― Pattern 2 ―――");
		for(EmpBean e : elist) {
			System.out.println(e.getEmpNo() + "\t" + e.getEname() + "\t" + e.getHireDate());
		}

		//Pattern 3. Single capture SQL
		sql =		"SELECT "
						+ "EMPNO AS empNo, "
						+ "ENAME AS ename, "
						+ "HIREDATE AS hireDate "
					+ "FROM "
						+ "EMP "
					+ "WHERE "
						//PK
						+ "EMPNO = ? ";
		p = new SqlParameter(1);
		p.addParameter(new BigDecimal(7369));

		//Get Bean
		EmpBean emp = jdbc.selectOne(sql, EmpBean.class, p);
		System.out.println("――― Pattern 3 ―――");
		System.out.println(emp.getEname());

		//Pattern 4. Update (insert,update,delete)
		sql =		"INSERT INTO DEPT "
						+ "(DEPTNO, DNAME, LOC) "
						+ "values "
						+ "(?, ?, ?) ";

		p = new SqlParameter(3);
		p.addParameter(new BigDecimal(99));
		p.addParameter("SPECIAL");
		p.addParameter("JAPAN");

		int i = jdbc.insert(sql, p);
		System.out.println("――― Pattern 4 ―――");
		System.out.println("The number of Inserts is"+i+"It is a matter");
		jdbc.commit();
		//Actually error trap and jdbc with catch.rollback()Run

		jdbc.close();
		//Actually error trapped and finally jdbc.close()Run
	}
}

The execution result is as follows.

Execution result (standard output)


――― Pattern 1 ―――
ACCOUNTING
RESEARCH
SALES
OPERATIONS
――― Pattern 2 ―――
7499	ALLEN	1981-02-20 00:00:00.0
7521	WARD	1981-02-22 00:00:00.0
7654	MARTIN	1981-09-28 00:00:00.0
7844	TURNER	1981-09-08 00:00:00.0
――― Pattern 3 ―――
SMITH
――― Pattern 4 ―――
The number of Inserts is 1.

Summary

Even if you compare from the article that I referred to There is almost no new content, The risk of causing a run-time error is increased because it is not type-safe. Personally, I handle at most three targets, ** SQL **, ** parameters **, and ** Bean **. If it's about messing up with Resultset and taking time I think it's worth the trade-off of type safety.

Also, in an environment where Java cannot be upgraded due to various reasons, In an environment where DB-based middleware and libraries such as ORM cannot be installed, Even if there are environmental and operational restrictions I like the fact that it can only run on older Java VMs **.

This time I targeted Oracle as the database If you can connect with JDBC, there are some dialectal differences in behavior. I'm optimistic that any database can be used.

Recommended Posts

JDBC connection mapper that eats SQL and returns beans
[About JDBC that connects Java and SQL]
Java-database connection Java-MySQL connection ①: JDBC and JDBC driver overview / September 2017