JDBC重新来过--解耦-封装-反射

原创
2018/01/16 16:07
阅读数 143

一.前言(废话)

JDBC是java数据库连接的简称,也算是一种协议,一种规定.

JDBC的规范由java定义,常用的接口和类在jdk的java.sql包下.

各个厂商均实现了java.sql下定义的接口.因此在java中访问各种数据库如oracle,mysql,sqlserver,psql等都有了统一的规范:

    加载驱动......

    获取连接......

    使用statement进行操作.....

    结果集遍历......

    关闭连接......

java.sql:

 

二.实现一个JDBCUtil,用以获取连接和关闭连接.

    特点:从外部加载配置文件,使用static{ }对属性进行初始化,然后使用单例模式实现线程安全(单线程非必要)

package com.rk.test1;

//省略import各种包


/*
 * JDBCUtil 实现了获取数据库连接,关闭数据库连接等操作.
 * 
 *
 * 
 * 这个类的特点:
 * 1.从外部配置文件读取数据库信息,与具体的数据库驱动解耦,可直接使用oracle的驱动,也可以直接使用mysql,sqlserver的驱动
 * 2.静态单例模式,该类只能提供一个全局的(static) Connection实例,防止过多的connection占用大量系统资源.
 * */

public class JDBCUtil {
	
	private static String driver;
	private static String url;
	private static String user;
	private static String password;
	
	private static Connection conn = null;
	
	//从配置文件初始化数据库连接信息
	static {
		InputStream inStream = 
             JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
		//初始化driver url name password
		Properties p = new Properties();
		try {
			p.load(inStream);
			//初始化参数
			driver = p.getProperty("driver");
			url = p.getProperty("url");
			user = p.getProperty("user");
			password = p.getProperty("password");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static Connection getConnection() {
		//1.加载驱动  利用Class.forName()类加载器进行加载driver,
		try {
			Class.forName(driver);
			//双重校验锁保证获取connection是线程安全的(单线程程序中非必要)
			if(conn == null) {
				synchronized(JDBCUtil.class){
					if(conn == null)
					conn = DriverManager.getConnection(url, user, password);
				}
			}
			return conn;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	//关闭方法
	public static void close() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		conn = null;
	}
}

 

这是一个通用的JDBCUtil,用以提供数据库连接和关闭数据库连接.

三.对JDBCUtil进行扩展,使用通用的增删改方法

    在实际使用过程中,普遍使用PreparedStatement进行参数化查询.而这过程中增删改这三个方法执行的过程如出一辙,我们可以使用这样的一个通用方法来定义增删改:

        public static int addDeleteUpdate(String sql,Object ...args) {
			//2.方法执行必须在数据库连接之后操作,若没有连接数据库则抛出异常
			if(conn == null) {
				try {
					throw new Exception("没有获取数据库连接!");
				} catch (Exception e) {
					e.printStackTrace();
				}
				return 0;
			}
			PreparedStatement pstmt = null;
			try {
				//准备pstmt
				pstmt = conn.prepareStatement(sql);
				//sql参数设置
				for (int i = 0; i < args.length; i++) {
					pstmt.setObject(i+1, args[i]);
				}
				//执行
				int rs = pstmt.executeUpdate();
				//返回
				return rs;
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					pstmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			return 0;
		}

这个方法可以定义在JDBCUtil这个类中,然后在这个类中在创建一个静态内部类用于提供这些方法.当然我们还是先看一下这三个方法:

        //通用删除方法 add delete update select
		public static int insert(String sql,Object ...args) {
			return addDeleteUpdate(sql,args);
		}
		
		public static int delete(String sql, Object ...args) {
			return addDeleteUpdate(sql,args);
		}
		
		public static int update(String sql, Object ...args) {
			return addDeleteUpdate(sql,args);
		}

有没有感受到,瞬间增删改变得简单明了!

那么select方法为什么要和增删改区分开来呢?

    因为select方法返回的是一个结果集,ResultSet,模型与增删改不一样,因此我们需要重新写一个通用的select方法.

四,通用的查找方法(查找一个和多个模型类似)

    如何定义一个通用的查找方法呢?

    首先我们明白,查找在得到ResultSet前的代码应该是一致的:

    //获取连接,使用pstmt执行excuteQuery()

    不同点在于获取结果集之后如何处理.如查找t_admin数据库表和t_user会得出不一样的列名和值.

    因此在处理结果集的时候需要使用不同的类型--泛型来处理:

    我们这样定义这个方法:

    

public static <T> T select(Class<T> clazz,String sql, Object ...args) {
    //.....
}

    public static 是可选的,视情况而定嘛.

    <T>表示该方法为泛型方法,方法持有泛型T,后面的返回值类型为T,传入T类型的类,用于制造T类型的实例.

    前面一部分代码用于得到ResultSet,应该为:

            if(conn == null) {
				try {
					throw new Exception("没有获取数据库连接!");
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			}
			PreparedStatement pstmt = null;
			try {
				//准备pstmt
				pstmt = conn.prepareStatement(sql);
				//sql参数设置
				for (int i = 0; i < args.length; i++) {
					pstmt.setObject(i+1, args[i]);
				}
				//执行
				ResultSet rs = pstmt.executeQuery();

    这段代码并不完整,但其功能非常明显,获取连接,然后,查找得出结果集.

    得出结果集之后就是此方法的精髓部分了.

    得到结果集后,我们从结果集拿出从mysql中查找到的数据 记录,然后放在<String,Object>的一个Map中,然后接着利用反射的方法创建一个传入的类类型.

    紧接着在利用反射的方法为创建的这个类调用其setter为变量进行赋值.这里的要求是entity的变量要和数据库列一 一对应.

    代码如下:

                //对结果集进行获取元数据操作
				ResultSetMetaData rsmd= rs.getMetaData();
				Map<String,Object> data = new HashMap<String,Object>();
				if(rs.next()) {
					for (int i = 0; i < rsmd.getColumnCount(); i++) {
						data.put(rsmd.getColumnLabel(i+1), rs.getObject(i+1));
					}
				}
				//然后反射生成对象
				if(!data.isEmpty()) {
					T obj = clazz.newInstance();
					//利用反射进行对象赋值.
					for(Entry<String,Object> entry:data.entrySet()) {
						ReflectionForSet.setFileds(obj,entry.getKey(),entry.getValue());
					}
					return obj;
				}
				
			} catch (SQLException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}finally {
				try {
					pstmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			return null;

    其中,ReflectionForSet.setFileds(obj,entry.getKey(),entry.getValue())  为自定义方法:

package com.rk.test1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionForSet {
	
	public static  void setFileds(Object obj,String filedName,Object value) {
		Method[] method = obj.getClass().getDeclaredMethods();
		//获取方法
		for (Method md : method) {
			if(md.getName().contains("set" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1))) {
				try {
					//System.out.println("即将被反射调用的方法:" + md.getName());
					md.invoke(obj, value);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}

	}
}

该自定义方法实现了通过查找传入的entity对象的setter方法为某个变量进行赋值操作.

五.总结

    1.将数据库配置文件从外部传入,解耦代码与具体相关数据库(也可以通过配置多个驱动加载不同数据库,从而避免配置文件).

    2.封装Connection的获取和close方法,提高安全和性能.

    3.封装CRUD操作的基本代码,最高程度降低相同代码的书写,使得在复杂业务逻辑中代码重复率大大降低.

    4.总归只是练手用,实际使用过程中也没人会这么用吧.大家都用数据连接池和DBUtils组件.不过作为学习和练手,这让我学到了好多知识.

 

六.最后奉献上完整的JDBCUtil代码:

JDBCUtil  /   ReflectionForSet    /   Test

-------------------------------------------------------------

JDBCUtil

package com.rk.test1;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;


/*
 * JDBCUtil 实现了获取数据库连接,关闭数据库连接等操作.
 * 
 * 同时提供通用的 增删改 方法(通过匿名内部类 + 类方法实现)
 * 
 * 这个类的特点:
 * 1.从外部配置文件读取数据库信息,与具体的数据库驱动解耦,可直接使用oracle的驱动,也可以直接使用mysql,sqlserver的驱动
 * 2.静态单例模式,该类只能提供一个全局的(static) Connection实例,防止过多的connection占用大量系统资源.
 * 3.提供通用的增删改查方法,通过静态内部类提供,体现了工具的特点.
 * 	 同时用户也可以不适用工具类提供的通用增删改查方法,自己定义方法也可,此时此类只提供获取connection和关闭connection方法
 * */

public class JDBCUtil {
	
	private static String driver;
	private static String url;
	private static String user;
	private static String password;
	
	private static Connection conn = null;
	
	//从配置文件初始化数据库连接信息
	static {
		InputStream inStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
		//初始化driver url name password
		Properties p = new Properties();
		try {
			p.load(inStream);
			//初始化参数
			driver = p.getProperty("driver");
			url = p.getProperty("url");
			user = p.getProperty("user");
			password = p.getProperty("password");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static Connection getConnection() {
		//1.加载驱动  利用Class.forName()类加载器进行加载driver,
		try {
			Class.forName(driver);
			//双重校验锁保证获取connection是线程安全的(单线程程序中非必要)
			if(conn == null) {
				synchronized(JDBCUtil.class){
					if(conn == null)
					conn = DriverManager.getConnection(url, user, password);
				}
			}
			return conn;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	//关闭方法
	public static void close() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		conn = null;
	}
	
	/*
	 * 静态内部类,工具类,提供通用的增删改查方法.
	 * 
	 * 
	 * */
	static class CRUDUtil{
		//1.定义一个通用插入方法
		public static int addDeleteUpdate(String sql,Object ...args) {
			//2.方法执行必须在数据库连接之后操作,若没有连接数据库则抛出异常
			if(conn == null) {
				try {
					throw new Exception("没有获取数据库连接!");
				} catch (Exception e) {
					e.printStackTrace();
				}
				return 0;
			}
			PreparedStatement pstmt = null;
			try {
				//准备pstmt
				pstmt = conn.prepareStatement(sql);
				//sql参数设置
				for (int i = 0; i < args.length; i++) {
					pstmt.setObject(i+1, args[i]);
				}
				//执行
				int rs = pstmt.executeUpdate();
				//返回
				return rs;
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					pstmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			return 0;
		}
		
		//通用删除方法 add delete update select
		public static int insert(String sql,Object ...args) {
			return addDeleteUpdate(sql,args);
		}
		
		public static int delete(String sql, Object ...args) {
			return addDeleteUpdate(sql,args);
		}
		
		public static int update(String sql, Object ...args) {
			return addDeleteUpdate(sql,args);
		}
		
		//通用的select方法
		/**
		 * 	查询某一个
		 *	返回值类型:泛型T
		 *  参数:传入一个T类型的Class,利用此Class通过反射创建一个实例对象,然后对对象进行赋值(利用反射的方法),最后对象类型
		 * 
		 * */
		/**
		 * 首先第一步得到 ResultSet
		 * 然后通过ResultSet获得ResultSetMetaData
		 * 然后获取列的别名,把列的别名和列的值(object类型,因为不知道是int还是String)放入map
		 * 然后遍历map,利用反射把map中的kv装载到T类型的对象中.在返回T就可以了.
		 * 
		 * */
		public static <T> T select(Class<T> clazz,String sql, Object ...args) {
			if(conn == null) {
				try {
					throw new Exception("没有获取数据库连接!");
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			}
			PreparedStatement pstmt = null;
			try {
				//准备pstmt
				pstmt = conn.prepareStatement(sql);
				//sql参数设置
				for (int i = 0; i < args.length; i++) {
					pstmt.setObject(i+1, args[i]);
				}
				//执行
				ResultSet rs = pstmt.executeQuery();
				//对结果集进行获取元数据操作
				ResultSetMetaData rsmd= rs.getMetaData();
				Map<String,Object> data = new HashMap<String,Object>();
				if(rs.next()) {
					for (int i = 0; i < rsmd.getColumnCount(); i++) {
						data.put(rsmd.getColumnLabel(i+1), rs.getObject(i+1));
					}
				}
				//然后反射生成对象
				if(!data.isEmpty()) {
					T obj = clazz.newInstance();
					//利用反射进行对象赋值.
					for(Entry<String,Object> entry:data.entrySet()) {
						ReflectionForSet.setFileds(obj,entry.getKey(),entry.getValue());
					}
					return obj;
				}
				
			} catch (SQLException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				try {
					pstmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			return null;
		}
	}
}

ReflectionForSet 

package com.rk.test1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionForSet {
	
	public static  void setFileds(Object obj,String filedName,Object value) {
		Method[] method = obj.getClass().getDeclaredMethods();
		//获取方法
		for (Method md : method) {
			if(md.getName().contains("set" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1))) {
				try {
					//System.out.println("即将被反射调用的方法:" + md.getName());
					md.invoke(obj, value);
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}

test

package com.rk.test1;

import java.sql.Connection;

public class Test {
	
	public static void main(String[] args) {
		
		Connection conn = JDBCUtil.getConnection();
		
		System.out.println(conn);
		
//		String sql  = "select * from t_user where ";
		
//		try {
//			Statement stmt  = conn.createStatement();
//			ResultSet rs = stmt.executeQuery(sql);
//			
//			while(rs.next()) {
//				System.out.println("id:" + rs.getInt(1) + " stuid:" + rs.getInt(2) + " name:" + rs.getString(3) + " password:" + rs.getString(4) + " oldpasword:" + rs.getString(5));
//			}
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
		
		//增加
//		String sql = "insert into t_user(stuid,account,password0,oldpassword)values(?,?,?,?)";
//		int stuid = 20140162;
//		String account = "kun";
//		String password0 = "kun";
//		String oldpassword = "dsd";
//		long startTime = System.currentTimeMillis();
//		for(int i = 0; i < 2; i++) {
//			JDBCUtil.CRUDUtil.insert(sql, 30140564+i+1+100001,account,password0,oldpassword);
//		}
//		long endTime = System.currentTimeMillis();
//		System.out.println("插入完成,耗时:" + (endTime - startTime) + "ms");
		
		//删除
//		startTime = System.currentTimeMillis();
//		String sql2 = "delete from t_user where password0=?";
//		String password = "ruan";
//		JDBCUtil.CRUDUtil.delete(sql2, password);
//		endTime = System.currentTimeMillis();
//		System.out.println("删除完成,耗时:" + (endTime - startTime) + "ms");

		
		//更新
//		long startTime = System.currentTimeMillis();
//		String sql3 = "update t_user set account = ? where id = ?";
//		String accounts = "mrruan";
//		int id = 1;
//		JDBCUtil.CRUDUtil.update(sql3, accounts, id);
//		long endTime = System.currentTimeMillis();
//		System.out.println("更新完成,耗时:" + (endTime - startTime)/1000d + "s");
//		
		
		//查找
		String sql4 = "select id,userid,state from t_admin where id = ?";
		Admin admin = JDBCUtil.CRUDUtil.select(Admin.class, sql4, 1);
		System.out.println(admin);
		
		
		
		JDBCUtil.close();
	}
	
	
}

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部