摘要:前者是數(shù)據(jù)庫驅(qū)動(dòng),由于這是個(gè)挖坑性質(zhì)的東西,所以只針對(duì)做功能了后者是代碼生成框架,挺好用的,強(qiáng)烈推薦也就是說,并不使用常見的數(shù)據(jù)庫連接池,比如。的工廠已經(jīng)被初始化了,不能再對(duì)其進(jìn)行配置。
在以往的編碼中,使用過 spring-data-jpa,也用過 hibernate 和 mybatis。在簡單的數(shù)據(jù)庫操作中,spring-data-jpa 是用起來最爽的,畢竟在 IntelliJ IDEA 中可以獲得如下體驗(yàn):
瞧瞧,實(shí)體類屬性推導(dǎo),查詢條件推導(dǎo)。聲明完接口就可以用了,一行sql都不用敲,多爽 : P
在這里就不討論這三個(gè)框架的優(yōu)劣了,畢竟就我目前的使用場景而言,也體會(huì)不太出來到底誰好用...畢竟復(fù)雜的
SQL查詢都是要 類似hql或者XML 的解決方案來做的。
本著挖坑學(xué)習(xí)的精神,今天開始會(huì)試著一步一步做出一個(gè)自己的數(shù)據(jù)庫幫助庫 (不敢叫框架,畢竟#行業(yè)標(biāo)準(zhǔn)里太多 feature,實(shí)力不夠,做不來 ORZ).
今天就做個(gè)雛形吧,雛形的意思就是:看起來好像完成了一些功能,但只是實(shí)驗(yàn)性得編碼 : P
說明這個(gè)幫助庫就命名為 ice 吧,請?jiān)?起名字困難癥 ORZ
配置 Configuration這是一個(gè) 筆記 類型的文章,所有可能會(huì)有一些 啊 寫到這里才想起來 這樣的情況...
本文只引用 mysql-connecter 和 lombok 這兩個(gè)包。
前者是數(shù)據(jù)庫驅(qū)動(dòng),由于這是個(gè)挖坑性質(zhì)的東西,所以只針對(duì) MYSQL 做功能了;
后者是代碼生成框架,挺好用的,強(qiáng)烈推薦也就是說, ice 并不使用常見的數(shù)據(jù)庫連接池,比如 druid、cp30。而是自己實(shí)現(xiàn)一個(gè)緩存連接獲取器,畢竟挖坑就挖深點(diǎn)嘛哈哈。
本文假定讀者具備一定的 Java 能力,比如 反射、代理 這兩個(gè)點(diǎn),有興趣可以看看我之前的文章。
用過前邊所說的三個(gè)框架的同學(xué)肯定配過配置文件對(duì)吧,我一般配合 spring-boot 使用 spring-data-jpa,所以在 application.properties 配置;其他兩個(gè)框架則是在傳統(tǒng)的 SSH、SSM 環(huán)境下配置 application-*.xml。
既然是雛形,那么 ice 前期就直接 code-based configuration 了 (才不是偷懶...)
/** * Created by krun on 2017/9/22. */ @Builder @Data @NoArgsConstructor @AllArgsConstructor public class Configuration { private String driverClass; //驅(qū)動(dòng)類名 private String connectionURL; //連接url private String username; //數(shù)據(jù)庫用戶名 private String password; //數(shù)據(jù)庫密碼 }
好,配置就搞定啦,畢竟常見的連接參數(shù)都可以直接在 connectionURL 中附帶嘛。
連接供應(yīng)者 ConnectionProvider/** * Created by krun on 2017/9/22. */ public class ConnectionProvider{ /** * 不直接用構(gòu)造器而是用這種方式獲取實(shí)例,純粹是我個(gè)人喜好,感覺這樣更有 "通過配置得到" 的意思。 */ public static CachedConnection configure (Configuration configuration) { return new CachedConnection(configuration); } private Class driverClass = null; private Configuration configuration; private volatile Connection connection; private CachedConnection (Configuration configuration) { this.configuration = configuration; try { // 加載驅(qū)動(dòng) this.driverClass = Class.forName(this.configuration.getDriverClass( )); } catch (ClassNotFoundException e) { throw new RuntimeException("無法加載 JDBC 驅(qū)動(dòng): " + this.configuration.getDriverClass( )); } } // 內(nèi)部用來獲取一個(gè)新連接 private synchronized Connection create ( ) { // 檢查是否已經(jīng)加載驅(qū)動(dòng),沒有的話拋出異常。 if (driverClass == null) { throw new RuntimeException("尚未加載 JDBC 驅(qū)動(dòng)."); } else { try { // 獲取一個(gè)新連接 return DriverManager.getConnection(this.configuration.getConnectionURL( ), this.configuration.getUsername( ), this.configuration.getPassword( )); } catch (SQLException e) { throw new RuntimeException(e); } } } // 暴露給外界獲取一個(gè)連接,在這里進(jìn)行 "是否有可用連接" 和 "連接有效性檢查" public synchronized Connection provide( ) throws SQLException { if (connection == null) { connection = createConnection( ); } else if (connection.isClosed( )) { connection = createConnection( ); } return connection; } }Repository模板 Repository
這個(gè)完全是受 spring-data-jpa 的影響,我覺得"方法映射數(shù)據(jù)庫操作"的映射方式是最吼的,只是 JPA 的接口更簡潔些。
/** * Created by krun on 2017/9/22. */ public interface Repository{ List findAll(); //獲取表內(nèi)所有元素 E save(E e); //保存元素,當(dāng)元素存在id時(shí),嘗試更新(update);不存在id時(shí),嘗試插入(insert) long delete(E e); //刪除元素 boolean exist(E e); //判斷給定元素是否存在 }
考慮到實(shí)現(xiàn)難度,現(xiàn)在不打算做"方法名解析到sql語句"。因此還是直接引入一個(gè) @Query 注解來設(shè)置方法對(duì)應(yīng)的 SQL 操作:
/** * Created by krun on 2017/9/22. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Query { // 暫時(shí)也不做別名處理了 String value(); }
約定 @Query 注解中的 SQL 語句使用 %s 占位符指明表名(這由 Repository
那么結(jié)合一下,現(xiàn)在的模板應(yīng)該是這樣的:
/** * Created by krun on 2017/9/22. */ public interface RepositoryRepository工廠 RepositoryFactory{ @Query("SELECT * FROM %s") List findAll(); ... }
現(xiàn)在用戶可以繼承 Repository 接口來聲明一個(gè)指定實(shí)體類的 repository,我們需要一個(gè)工廠類來為這些接口類創(chuàng)建代理對(duì)象(Proxy)以注入我們的方法攔截器。
/** * Created by krun on 2017/9/22. */ public class RepositoryFactory { //全局工廠的名字 private static final String GLOBAL_FACTORY = "GLOBAL"; //用來保存給定名稱和其對(duì)應(yīng)的工廠實(shí)例 private static final LinkedHashMapRepository的靈魂 RepositoryInvocationHandlerfactoryMap; static { factoryMap = new LinkedHashMap<>(); } // 這與之前 Connection.configure 的寫法一樣,純粹個(gè)人喜好。 public static RepositoryFactory configure(Configuration configure) { return RepositoryFactory.configure(GLOBAL_FACTORY, configure); } public static RepositoryFactory configure(String name, Configuration configure) { if (RepositoryFactory.factoryMap.get(name) == null) { synchronized ( RepositoryFactory.factoryMap ) { if (RepositoryFactory.factoryMap.get(name) == null) { RepositoryFactory.factoryMap.put(name, new RepositoryFactory(ConnectionProvider.configure(configure))); } else { throw new RuntimeException(name + " 的工廠已經(jīng)被初始化了,不能再對(duì)其進(jìn)行配置。"); } } } return RepositoryFactory.factoryMap.get(name); } public synchronized static RepositoryFactory get() { return RepositoryFactory.get(GLOBAL_FACTORY); } public synchronized static RepositoryFactory get(String name) { return RepositoryFactory.factoryMap.get(name); } // 每個(gè)工廠類實(shí)例都持有一個(gè)自己的 連接提供者,因?yàn)槎鄶?shù)情況下全局只會(huì)有一個(gè)工廠類實(shí)例... @Getter private ConnectionProvider connectionProvider; //用于保存每個(gè)工廠實(shí)例所創(chuàng)建的 repository 實(shí)例,用以復(fù)用,避免重復(fù)創(chuàng)建 repository 實(shí)例。 private final LinkedHashMap , Repository> repositoryMap; private RepositoryFactory(ConnectionProvider connectionProvider) { this.connectionProvider = connectionProvider; this.repositoryMap = new LinkedHashMap<>(); } // 為 Repository 接口創(chuàng)建代理實(shí)例,并注入我們自己的方法攔截器:RepositoryInvocationHandler @SuppressWarnings("unchecked") private > T getProxy(Class repositoryClass) { return (T) Proxy.newProxyInstance(repositoryClass.getClassLoader(), new Class[] {repositoryClass}, new RepositoryInvocationHandler(this, repositoryClass)); } // 獲取給定 repository 類型的代理實(shí)例 @SuppressWarnings("unchecked") public > T getRepository(Class repositoryClass) { T repository; if ((repository = (T) repositoryMap.get(repositoryClass)) == null) { synchronized ( repositoryMap ) { if ((repository = (T) repositoryMap.get(repositoryClass)) == null) { repository = getProxy(repositoryClass); repositoryMap.put(repositoryClass, repository); } } } return repository; } }
我們剛才在 RepositoryFactory.getProxy 中創(chuàng)建了一個(gè)RepositoryInvocationHandler實(shí)例,并傳入了RepositoryFactory實(shí)例以及代理的Repository類型。
這因?yàn)樵诜椒〝r截器中,我們需要獲取一些東西:
操作的實(shí)體類的類型,因?yàn)樗娜懶问骄褪菍?shí)體類所代表的表的名字
通過工廠類實(shí)例獲取一個(gè) connection
/** * Created by krun on 2017/9/22. */ public class RepositoryInvocationHandler implements InvocationHandler { private RepositoryFactory factory; //用于保存repository的泛型信息,后面可以比較方便地獲取,雖然也可以通過 "method.getDeclaringClass()" 來獲取,但總覺得麻煩了些。 private Class extends Repository> invokeRepositoryClass; public RepositoryInvocationHandler (RepositoryFactory factory, Class extends Repository> invokeRepositoryClass) { this.factory = factory; this.invokeRepositoryClass = invokeRepositoryClass; } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName( ); // 根據(jù)方法名選擇合適的 handle方法,以后應(yīng)該是要改成表驅(qū)動(dòng),不然太多 if-else 了 ORZ // 說起來,表驅(qū)動(dòng)的話,就有合適的地方暴露接口給用戶修改方法映射邏輯了。 if (methodName.startsWith("find")) { return handleFind(method, args); } else if (methodName.startsWith("save")) { } else if (methodName.startsWith("delete")) { } else if (methodName.startsWith("exist")) { } return null; } // 通過保存的 invokeRepositoryClass 獲取其持有的泛型信息 private String getEntityName () { if (! Repository.class.isAssignableFrom(this.invokeRepositoryClass)) { throw new RuntimeException(String.format("接口 [%s] 并沒有繼承 Repository", this.invokeRepositoryClass.getName( ))); } // 這里沒有做太多考慮,暫時(shí)沒遇到問題而已... ParameterizedType parameterizedType = (ParameterizedType) this.invokeRepositoryClass.getGenericInterfaces()[0]; return ((Class)parameterizedType.getActualTypeArguments()[0]).getSimpleName().toLowerCase(); } @SuppressWarnings("unchecked") private Object handleFind (Method method, Object... args) { // 獲取方法上的 @Query 注解 Query query = method.getAnnotation(Query.class); if (query == null) { throw new IllegalArgumentException("也許你忘了為 " + method.getDeclaringClass( ).getSimpleName( ) + "." + method.getName( ) + "() 設(shè)置 @Query 注解"); } // java 7的 "try-with-resource" 語法糖,挺方便的,不用操心 connection 關(guān)沒關(guān)了 // 突然想起來,這樣寫的話好像... ConnectionProvider 就沒用了啊 ... ORZ try (Connection connection = factory.getConnectionProvider().provide()) { PreparedStatement preparedStatement = (PreparedStatement) connection //簡單得替換一下表名占位符 .prepareStatement(String.format(query.value(), getEntityName())); // 粗暴得把參數(shù)都塞進(jìn)去... // 以后估計(jì)要做個(gè) switch-case 把參數(shù)類型檢查做一下 for (int i = 1; i <= args.length; i++) { preparedStatement.setObject(i, args[i - 1]); } System.out.println(preparedStatement.asSql()); // 把結(jié)果打出來看看 ResultSet resultSet = preparedStatement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); while (resultSet.next()) { for (int i = 1; i <= metaData.getColumnCount(); i++) { System.out.print(String.valueOf(resultSet.getObject(i)) + " "); } System.out.println(); } resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } // 同樣的簡單粗暴,只為了看效果哈哈 try { // 注:這種寫法在 "List最后findAll()" 這種情況會(huì)報(bào)錯(cuò),因?yàn)?List 是接口,無法為其創(chuàng)建實(shí)例 return method.getReturnType().newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace( ); } return null; } }
/** * Created by krun on 2017/9/22. */ public class App { @Data public static class Student { private String id; private String name; } interface StudentRepository extends Repository{ @Query("SELECT * FROM %s WHERE gender = ?") List findByGender(String gender); @Query("SELECT * FROM %s WHERE id > ?") List findByIdAfter(String id); @Query("SELECT * FROM %s WHERE name = ?") Student findByName(String name); } public static void main(String[] args ) { RepositoryFactory factory = RepositoryFactory.configure(Configuration.builder() .driverClass("com.mysql.jdbc.Driver") .connectionURL("jdbc:mysql://localhost:3306/hsc") .username("gdpi") .password("gdpi") .build()); StudentRepository studentRepository = factory.getRepository(StudentRepository .class); studentRepository .findByName("krun"); } } > SELECT * FROM student WHERE name = "krun" > 20152200000 計(jì)算機(jī)技術(shù)系 男 2015 軟件技術(shù) krun
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/70527.html
摘要:事實(shí)上,實(shí)現(xiàn)了接口,而也實(shí)現(xiàn)了接口。還記得之前說的,使用之后,其返回的實(shí)際上是一個(gè)裝飾器嗎。所以修改如下是默認(rèn)全局工廠名稱,請使用別的名稱工廠已經(jīng)配置完成,請不要重復(fù)配置。 這是做個(gè)數(shù)據(jù)庫幫助庫雛形 的當(dāng)晚的再一次嘗試 ORZ 在意識(shí)到原來的 ConnectionProvider 提供的只是一個(gè)普通(實(shí)現(xiàn)了AutoCloseable接口)的 Connection,這在 Reposito...
摘要:注意供應(yīng)器只會(huì)在倉庫工廠第一次創(chuàng)建工廠時(shí)調(diào)用,而參數(shù)處理器和結(jié)果解析器將在每次倉庫方法被調(diào)用時(shí)調(diào)用。解析器接收一個(gè)語句表模型的類聲明觸發(fā)解析器的倉庫方法聲明。因此當(dāng)您配置了一個(gè)結(jié)果解析器,語句的執(zhí)行時(shí)機(jī)將推遲到這里。 Juice 這是我自己做的一個(gè)小項(xiàng)目,也可能會(huì)棄坑... 留作紀(jì)念吧。GitHub 地址 簡介 Juice 是一個(gè)簡易的、尚不完善的基于 Java 的SQL數(shù)據(jù)庫工具,它...
摘要:沒有耐心閱讀的同學(xué),可以直接前往學(xué)習(xí)全棧最后一公里。我下面會(huì)羅列一些,我自己錄制過的一些項(xiàng)目,或者其他的我覺得可以按照這個(gè)路線繼續(xù)深入學(xué)習(xí)的項(xiàng)目資源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技術(shù)軟文,閱讀需謹(jǐn)慎,長約 7000 字,通讀需 5 分鐘 大家好,我是 Scott,本文通過提供給大家學(xué)習(xí)的方法,...
摘要:使用,開發(fā)者用來表示異步數(shù)據(jù)流,通過操作符來查詢異步數(shù)據(jù)量,并使用來參數(shù)化異步數(shù)據(jù)流中的并發(fā)。在中,你可以表述多個(gè)異步數(shù)據(jù)流,并且使用對(duì)象訂閱事件流。因?yàn)樾蛄惺菙?shù)據(jù)流,你可以使用由擴(kuò)展方法實(shí)現(xiàn)的標(biāo)準(zhǔn)查詢操作符來查詢它們。 對(duì) 我又挖坑了 不過其實(shí)也不算挖坑 因?yàn)閡i構(gòu)建中就會(huì)有填坑的文章 之前一直在寫《編寫大型web頁面 結(jié)合現(xiàn)有前端形勢思考未來前端》這是一篇巨難寫的文章 估計(jì)要到年中...
閱讀 3659·2021-09-22 15:15
閱讀 3564·2021-08-12 13:24
閱讀 1312·2019-08-30 15:53
閱讀 1824·2019-08-30 15:43
閱讀 1185·2019-08-29 17:04
閱讀 2795·2019-08-29 15:08
閱讀 1583·2019-08-29 13:13
閱讀 3088·2019-08-29 11:06