源碼地址java
公司的持久層採用的hibernate框架,這也是不少公司使用的一種持久層框架。它將瞬時態的javabean對象轉化爲持久態數據表的字段對象、或將持久態的字段對象轉化爲瞬時態javabean對象。我比較喜歡看源碼,看別人的架構思想,由於,筆者想向架構師的方向進發。看了別人的源碼,忽然想模擬hibernate框架,本身寫個框架出來。 這裏去除了hibernate框架晦澀的地方,當作本身學習材料仍是不錯的。裏面涉及到反射、鏈接池等等。 這個項目中,你能夠知道數據庫鏈接池是怎麼建的,又是怎麼回收的。 使用靜態代碼塊加載配置文件mysql
如下詳細介紹我我的的項目,但確定沒有人家源碼寫得好,這裏僅做爲學習使用。git
若是不懂的,能夠私信我。sql
本項目以idea爲開發環境和以maven搭建的,分爲java包和test包。java包的配置文件放在resources下,代碼放在com.zby.simulationHibernate包下,以下是配置文件:數據庫
咱們在使用hibernate時,通常會配置鏈接池,好比,初始化鏈接數是多少,最大鏈接數是多少?這個鏈接的是什麼?咱們在啓動項目時,hibernate根據初始的鏈接數,來建立多少個數據庫鏈接對象,也就是jdbc中的Connection對象。apache
爲何要有這個鏈接池?由於,每次開啓一個鏈接和關閉一個鏈接都是消耗資源的,咱們開啓了這些鏈接對象以後,把它們放在一個容器中,咱們什麼時候須要什麼時候從容器中取出來。當不須要的時候,再將踏進放回到容器中。於是,能夠減小佔用的資源。session
以下,是初始化的鏈接對象:架構
package com.zby.simulationHibernate.util.factory; import com.zby.simulationHibernate.util.exception.GenericException; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.io.InputStream; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; /** * Created By zby on 21:23 2019/1/23 * 數據庫的鏈接 */ public class Connect { /** * 鏈接池的初始值 */ private static int initPoolSize = 20; /** * 建立property的配置文件 */ protected static Properties properties; /** * 鏈接池的最小值 */ protected static int minPoolSize; /** * 鏈接池的最大值 */ protected static int maxPoolSize; //【2】靜態代碼塊 static { //加載配置文件 properties = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("db.properties"); try { properties.load(is); minPoolSize = Integer.valueOf(properties.getProperty("jdbc.minConnPool")); if (minPoolSize <= initPoolSize) minPoolSize = initPoolSize; maxPoolSize = Integer.valueOf(properties.getProperty("jdbc.maxConnPool")); if (minPoolSize > maxPoolSize) throw new GenericException("鏈接池的最小鏈接數不能大於最大鏈接數"); } catch (IOException e) { System.out.println("未找到配置文件"); e.printStackTrace(); } } /** * Created By zby on 16:50 2019/1/23 * 獲取數據鏈接 */ protected java.sql.Connection createConnect() { String driverName = properties.getProperty("jdbc.driver"); if (StringUtils.isEmpty(driverName)) { driverName = "com.mysql.jdbc.Driver"; } String userName = properties.getProperty("jdbc.username"); String password = properties.getProperty("jdbc.password"); String dbUrl = properties.getProperty("jdbc.url"); try { Class.forName(driverName); return DriverManager.getConnection(dbUrl, userName, password); } catch (ClassNotFoundException e) { System.out.println("找不到驅動類"); e.printStackTrace(); } catch (SQLException e) { System.out.println("加載異常"); e.printStackTrace(); } return null; } }
咱們在使用hibernate時,不是直接使用鏈接對象,而是,以會話的方式建立一個鏈接。建立會話的方式有兩種。一種是openSession,這種是手動提交事務。getCurrentSession是自動提交事務。app
如代碼所示:框架
package com.zby.simulationHibernate.util.factory; import java.sql.Connection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created By zby on 15:43 2019/1/23 */ public class SqlSessionFactory implements SessionFactory { /** * 鏈接池 */ private static List<Connection> connections; /** * 鏈接對象 * * @return */ private static Connect connect = new Connect(); protected static List<Connection> getConnections() { return connections; } //靜態代碼塊,初始化常量池 static { connections = new ArrayList<>(); Connection connection; for (int i = 0; i < Connect.minPoolSize; i++) { connection = connect.createConnect(); connections.add(connection); } } @Override public Session openSession() { return getSession(false); } @Override public Session getCurrentSession() { return getSession(true); } /** * 獲取session * * @param autoCommit 是否自動提交事務 * @return */ private Session getSession(boolean autoCommit) { //【1】判斷鏈接池有可用的鏈接對象 boolean hasNoValidConn = hasValidConnction(); //【2】沒有可用的鏈接池,使用最大的鏈接池 if (!hasNoValidConn) { for (int i = 0; i < (Connect.maxPoolSize - Connect.minPoolSize); i++) { connections.add(connect.createConnect()); } } //【3】有可用的鏈接 for (Iterator iterator = connections.iterator(); iterator.hasNext(); ) { Connection connection = null; try { connection = (Connection) iterator.next(); connection.setAutoCommit(autoCommit); Session session = new Session(connection); iterator.remove(); return session; } catch (Exception e) { e.printStackTrace(); } } return null; } /** * Created By zby on 21:50 2019/1/23 * 當咱們沒開啓一個鏈接,鏈接池的數目減小1,直到鏈接池的數量爲0 */ private boolean hasValidConnction() { return null != connections && connections.size() != 0; } }
咱們既然使用這個框架,必然要有數據查找的功能。返回結果分爲兩種,一種是以實體類直接返回,調用addEntity方法。可是,多數狀況下,是多張表聯合查詢的結果,這種狀況下,直接以實體類接確定不能夠的。於是,咱們須要自定義接收對象,並將查找結果進行過濾,再封裝成咱們想要的對象。
/** * Created By zby on 23:19 2019/1/23 * 體檢反射的實體類 */ public SqlQuery addEntity(Class<T> persistenceClass) { this.persistenceClass = persistenceClass; return this; }
/** * Created By zby on 19:18 2019/1/27 * 建立類型 */ public SqlQuery addScalar(String tuple, String alias) { if (CommonUtil.isNull(aliasMap)) { aliasMap = new HashMap<>(); } for (Map.Entry<String, String> entry : aliasMap.entrySet()) { String key = entry.getKey(); if (key.equals(tuple)) throw new GenericException("alias已經存在,即alias=" + key); String value = aliasMap.get(key); if (value.equals(alias) && key.equals(tuple)) throw new GenericException("當前alias的type已經存在,alias=" + key + ",type=" + value); } aliasMap.put(tuple, alias); return this; } /** * Created By zby on 9:20 2019/1/28 * 數據轉換問題 */ public SqlQuery setTransformer(ResultTransformer transformer) { if (CommonUtil.isNull(aliasMap)) { throw new IllegalArgumentException("請添加轉換的屬性數量"); } transformer.transformTuple(aliasMap); this.transformer = transformer; return this; }
/** * Created By zby on 17:02 2019/1/29 * 設置查找參數 */ public SqlQuery setParamter(int start, Object param) { if (CommonUtil.isNull(columnParamer)) columnParamer = new HashMap<>(); columnParamer.put(start, param); return this; } /** * Created By zby on 16:41 2019/1/24 * 查找值 */ public List<T> list() { PreparedStatement statement = null; ResultSet resultSet = null; try { statement = connection.prepareStatement(sql); if (CommonUtil.isNotNull(columnParamer)) { for (Map.Entry<Integer, Object> entry : columnParamer.entrySet()) { int key = entry.getKey(); Object value = entry.getValue(); statement.setObject(key + 1, value); } } resultSet = statement.executeQuery(); PersistentObject persistentObject = new PersistentObject(persistenceClass, resultSet); if (CommonUtil.isNotNull(aliasMap)) return persistentObject.getPersist(transformer); return persistentObject.getPersist(); } catch (Exception e) { e.printStackTrace(); } finally { SessionClose.closeConnStateResSet(connection, statement, resultSet); } return null; }
/** * Created By zby on 16:41 2019/1/24 * 查找值 */ public T uniqueResult() { List<T> list = list(); if (CommonUtil.isNull(list)) return null; if (list.size() > 1) throw new GenericException("原本須要返回一個對象,卻返回 " + list.size() + "個對象"); return list.get(0); }
@Test public void testList() { Session session = new SqlSessionFactory().openSession(); String sql = "SELECT " + " customer_name AS customerName, " + " `name` AS projectName " + "FROM " + " project where id >= ? and id <= ?"; SqlQuery query = session.createSqlQuery(sql); query.setParamter(0, 1); query.setParamter(1, 2); query.addScalar("customerName", StandardBasicTypes.STRING) .addScalar("projectName", StandardBasicTypes.STRING); query.setTransformer(Transforms.aliasToBean(ProjectData.class)); List<ProjectData> projects = query.list(); for (ProjectData project : projects) { System.out.println(project.getCustomerName() + " " + project.getProjectName()); } } @Ignore public void testListNoData() { Session session = new SqlSessionFactory().openSession(); String sql = "SELECT " + " customer_name AS customerName, " + " `name` AS projectName " + "FROM " + " project where id >= ? and id <= ?"; SqlQuery query = session.createSqlQuery(sql). setParamter(0, 1). setParamter(1, 2). addEntity(Project.class); List<Project> projects = query.list(); for (Project project : projects) { System.out.println(project.getCustomerName() + " " + project.getGuestCost()); } }
咱們這裏以 merge 方法來保存數據,由於這個方法很是的特殊,咱們在這裏作特殊說明。若是該瞬時態的對象有主鍵,並且,其在數據表中已經存在該主鍵的字段對象,咱們此時就更新該數據表。若是數據表中沒有當前主鍵的字段對象,咱們向數據庫中添加該對象的值。若是該瞬時態的對象沒有主鍵,咱們直接在數據表中添加該對象。
如代碼所示:
/** * Created By zby on 15:41 2019/1/29 * 合併,首先判斷id是否存在,若id存在則更新,若id不存在,則保存數據 */ public T merge(T t) { if (CommonUtil.isNull(t)) throw new IllegalArgumentException("參數爲空"); Class<T> clazz = (Class<T>) t.getClass(); Field[] fields = clazz.getDeclaredFields(); boolean isContainsId = CommonUtil.isNotNull(PropertyUtil.containId(fields)) ? true : false; long id = PropertyUtil.getIdValue(fields, t, propertyAccessor); if (isContainsId) { return id > 0L ? update(t) : save(t); } return save(t); } /** * Created By zby on 17:37 2019/1/29 * 保存數據 */ public T save(T t) { if (CommonUtil.isNull(t)) throw new RuntimeException("不能保存空對象"); PreparedStatement statement = null; ResultSet resultSet = null; StringBuilder columnJoint = new StringBuilder(); StringBuilder columnValue = new StringBuilder(); try { Field[] fields = t.getClass().getDeclaredFields(); String sql = " insert into " + ClassUtil.getClassNameByGenericity(t) + "("; for (int i = 0; i < fields.length; i++) { String propertyName = fields[i].getName(); Object propertyValue = propertyAccessor.getPropertyValue(t, propertyName); if (CommonUtil.isNotNull(propertyValue)) { String columnName = PropertyUtil.propertyNameTransformColumnName(propertyName, true); if (StandardBasicTypes.BOOLEAN.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append("is_" + columnName + ","); columnValue.append(propertyValue + ","); } else if (StandardBasicTypes.LONG.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.FLOAT.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.DOUBLE.equalsIgnoreCase(fields[i].getGenericType().toString()) || StandardBasicTypes.INTEGER.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append(columnName + ","); columnValue.append(propertyValue + ","); } else if (StandardBasicTypes.DATE.equalsIgnoreCase(fields[i].getGenericType().toString())) { columnJoint.append(columnName + ","); columnValue.append("'" + DateUtil.SIMPLE_DATE_FORMAT.format((Date) propertyValue) + "',"); } else { columnJoint.append(columnName + ","); columnValue.append("'" + propertyValue + "',"); } } } columnJoint = StringUtil.replace(columnJoint, ","); columnValue = StringUtil.replace(columnValue, ","); sql += columnJoint + ") VALUES(" + columnValue + ")"; System.out.println(sql); statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); statement.executeUpdate(); resultSet = statement.getGeneratedKeys(); while (resultSet.next()) { return load((Class<T>) t.getClass(), resultSet.getLong(1)); } return t; } catch (SQLException e) { System.out.println("保存數據出錯,實體對象爲=" + t); e.printStackTrace(); } finally { SessionClose.closeConnStateResSet(connection, statement, resultSet); } return null; }
@Test public void testSave() { Session session = new SqlSessionFactory().getCurrentSession(); Project project = new Project(); project.setCustomerName("hhhh"); project.setCreateDatetime(new Date()); project.setDeleted(true); project = (Project) session.save(project); System.out.println(project.getId()); }
有時,咱們只要根據當前對象的id,獲取當前對象的所有信息,於是,咱們能夠這樣寫:
/** * Created By zby on 16:36 2019/1/29 * 經過id獲取對象 */ public T load(Class<T> clazz, Long id) { if (CommonUtil.isNull(clazz)) throw new IllegalArgumentException("參數爲空"); String className = ClassUtil.getClassNameByClass(clazz); String sql = " select * from " + className + " where id= ? "; SqlQuery query = createSqlQuery(sql) .setParamter(0, id) .addEntity(clazz); return (T) query.uniqueResult(); }
測試代碼:
@Test public void testload() { Session session = new SqlSessionFactory().openSession(); Project project = (Project) session.load(Project.class, 4L); System.out.println(project); }
當咱們使用完該鏈接對象後,須要將對象放回到容器中,並非直接調用connection.close()方法,而是調用這個方法:
/** * Created By zby on 16:10 2019/3/17 * 獲取容器的對象,若是是關閉session,則將鏈接對象放回到容器中 * 若是是開啓session,則從容器中刪除該鏈接對象 */ protected static List<Connection> getConnections() { return connections; } /** * Created By zby on 22:45 2019/1/23 * <p> * 當關閉當前會話時,這並不是真正的關閉會話 * 只是將鏈接對象放回到鏈接池中 */ public static void closeConn(Connection connection) { SqlSessionFactory.getConnections().add(connection); }
寫框架實際上是不難的,難就難在如何設計框架。或者說,難就難在基礎不牢。若是基礎打不牢的話,很難往上攀升。