導讀
公司的持久層採用的hibernate框架,這也是很多公司使用的一種持久層框架。它將瞬時態的數據轉化爲持久態、或將持久態的數據轉化爲瞬時態數據。我比較喜歡看源碼,看別人的架構思想,因爲,筆者想向架構師的方向進發。看了別人的源碼,突然想模擬hibernate框架,自己寫個框架出來。 這裏去除了hibernate框架晦澀的地方,當做自己學習材料還是不錯的。裏面涉及到反射、連接池等等。 這個項目中,你可以知道數據庫連接池是怎麼建的,又是怎麼回收的。 使用警惕代碼塊加載配置文件
以下詳細介紹我個人的項目,但肯定沒有人家源碼寫得好,這裏僅作爲學習使用。
如果不懂的,可以私信我。
配置文件
本項目以idea爲開發環境和以maven搭建的,分爲java包和test包。java包的配置文件放在resources下,代碼放在com.zby.simulationHibernate包下,如下是配置文件:
連接池
我們在使用hibernate時,一般會配置連接池,比如,初始化連接數是多少,最大連接數是多少?這個連接的是什麼?我們在啓動項目時,hibernate根據初始的連接數,來創建多少個數據庫連接對象,也就是jdbc中的Connection對象。
爲什麼要有這個連接池?因爲,每次開啓一個連接和關閉一個連接都是消耗資源的,我們開啓了這些連接對象之後,把它們放在一個容器中,我們何時需要何時從容器中取出來。當不需要的時候,再將踏進放回到容器中。因而,可以減少佔用的資源。
如下,是初始化的連接對象:
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;
}
}
創建Session會話
我們在使用hibernate時,不是直接使用連接對象,而是,以會話的方式創建一個連接。創建會話的方式有兩種。一種是openSession,這種是手動提交事務。getCurrentSession是自動提交事務。
如代碼所示:
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());
}
}
保存數據
我們這裏以 merger來保存數據,因爲這個方法非常的特殊。如果該瞬時態的獨享有主鍵,而且,其在數據庫中依舊存在該主鍵的數據,我們此時就更新數據表。如果數據表中沒有當前主鍵的數據,我們向數據庫中添加該對象的值。如果該瞬時態的對象沒有主鍵,我們直接在數據表中添加該對象。
如代碼所示:
/**
* 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加載對象
有時,我們只要根據當前對象的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);
}
總結
寫框架其實是不難的,難就難在如何設計框架。或者說,難就難在基礎不牢。如果基礎打不牢的話,很難網上攀升。