国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[JAVA][學習·練手·挖坑] 做個數據庫幫助庫雛形

rickchen / 2757人閱讀

摘要:前者是數據庫驅動,由于這是個挖坑性質的東西,所以只針對做功能了后者是代碼生成框架,挺好用的,強烈推薦也就是說,并不使用常見的數據庫連接池,比如。的工廠已經被初始化了,不能再對其進行配置。

在以往的編碼中,使用過 spring-data-jpa,也用過 hibernatemybatis。在簡單的數據庫操作中,spring-data-jpa 是用起來最爽的,畢竟在 IntelliJ IDEA 中可以獲得如下體驗:

瞧瞧,實體類屬性推導,查詢條件推導。聲明完接口就可以用了,一行sql都不用敲,多爽 : P

在這里就不討論這三個框架的優劣了,畢竟就我目前的使用場景而言,也體會不太出來到底誰好用...畢竟復雜的
SQL查詢都是要 類似hql或者XML 的解決方案來做的。

本著挖坑學習的精神,今天開始會試著一步一步做出一個自己的數據庫幫助庫 (不敢叫框架,畢竟#行業標準里太多 feature,實力不夠,做不來 ORZ).

今天就做個雛形吧,雛形的意思就是:看起來好像完成了一些功能,但只是實驗性得編碼 : P

說明

這個幫助庫就命名為 ice 吧,請原諒 起名字困難癥 ORZ

這是一個 筆記 類型的文章,所有可能會有一些 啊 寫到這里才想起來 這樣的情況...

本文只引用 mysql-connecterlombok 這兩個包。
前者是數據庫驅動,由于這是個挖坑性質的東西,所以只針對 MYSQL 做功能了;
后者是代碼生成框架,挺好用的,強烈推薦

也就是說, ice 并不使用常見的數據庫連接池,比如 druidcp30。而是自己實現一個緩存連接獲取器,畢竟挖坑就挖深點嘛哈哈。
本文假定讀者具備一定的 Java 能力,比如 反射代理 這兩個點,有興趣可以看看我之前的文章。

配置 Configuration

用過前邊所說的三個框架的同學肯定配過配置文件對吧,我一般配合 spring-boot 使用 spring-data-jpa,所以在 application.properties 配置;其他兩個框架則是在傳統的 SSHSSM 環境下配置 application-*.xml

既然是雛形,那么 ice 前期就直接 code-based configuration 了 (才不是偷懶...)

/**
 * Created by krun on 2017/9/22.
 */
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Configuration {

    private String driverClass;   //驅動類名
    private String connectionURL; //連接url
    private String username;      //數據庫用戶名
    private String password;      //數據庫密碼

}

好,配置就搞定啦,畢竟常見的連接參數都可以直接在 connectionURL 中附帶嘛。

連接供應者 ConnectionProvider
/**
 * Created by krun on 2017/9/22.
 */
public class ConnectionProvider{

    /**
     * 不直接用構造器而是用這種方式獲取實例,純粹是我個人喜好,感覺這樣更有 "通過配置得到" 的意思。
     */
    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 {
            // 加載驅動
            this.driverClass = Class.forName(this.configuration.getDriverClass( ));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("無法加載 JDBC 驅動: " + this.configuration.getDriverClass( ));
        }
    }

    // 內部用來獲取一個新連接
    private synchronized Connection create ( ) {
        // 檢查是否已經加載驅動,沒有的話拋出異常。
        if (driverClass == null) {
            throw new RuntimeException("尚未加載 JDBC 驅動.");
        } else {
            try {
                // 獲取一個新連接
                return DriverManager.getConnection(this.configuration.getConnectionURL( ),
                        this.configuration.getUsername( ), this.configuration.getPassword( ));
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // 暴露給外界獲取一個連接,在這里進行 "是否有可用連接" 和 "連接有效性檢查"
    public synchronized Connection provide( ) throws SQLException {
        if (connection == null) {
            connection = createConnection( );
        } else if (connection.isClosed( )) {
            connection = createConnection( );
        }
        return connection;
    }

}
Repository模板 Repository

這個完全是受 spring-data-jpa 的影響,我覺得"方法映射數據庫操作"的映射方式是最吼的,只是 JPA 的接口更簡潔些。

/**
 * Created by krun on 2017/9/22.
 */
public interface Repository {

    List findAll();   //獲取表內所有元素

    E save(E e);         //保存元素,當元素存在id時,嘗試更新(update);不存在id時,嘗試插入(insert)

    long delete(E e);    //刪除元素

    boolean exist(E e);  //判斷給定元素是否存在
}

考慮到實現難度,現在不打算做"方法名解析到sql語句"。因此還是直接引入一個 @Query 注解來設置方法對應的 SQL 操作:

/**
 * Created by krun on 2017/9/22.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {

    // 暫時也不做別名處理了
    String value();

}

約定 @Query 注解中的 SQL 語句使用 %s 占位符指明表名(這由 Repository 中 E 解析而來),用 ? 占位符指定參數,這是為了方便直接把語句放入PreparedStatement使用。

那么結合一下,現在的模板應該是這樣的:

/**
 * Created by krun on 2017/9/22.
 */
public interface Repository {

    @Query("SELECT * FROM %s")
    List findAll();

    ...
}
Repository工廠 RepositoryFactory

現在用戶可以繼承 Repository 接口來聲明一個指定實體類的 repository,我們需要一個工廠類來為這些接口類創建代理對象(Proxy)以注入我們的方法攔截器。

/**
 * Created by krun on 2017/9/22.
 */
public class RepositoryFactory {

    //全局工廠的名字
    private static final String GLOBAL_FACTORY = "GLOBAL";
    
    //用來保存給定名稱和其對應的工廠實例
    private static final LinkedHashMap factoryMap;

    static {
        factoryMap = new LinkedHashMap<>();
    }

    // 這與之前 Connection.configure 的寫法一樣,純粹個人喜好。
    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 + " 的工廠已經被初始化了,不能再對其進行配置。");
                }
            }
        }
        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);
    }

    // 每個工廠類實例都持有一個自己的 連接提供者,因為多數情況下全局只會有一個工廠類實例...
    @Getter
    private ConnectionProvider connectionProvider;

    //用于保存每個工廠實例所創建的 repository 實例,用以復用,避免重復創建 repository 實例。
    private final LinkedHashMap, Repository> repositoryMap;

    private RepositoryFactory(ConnectionProvider connectionProvider) {
        this.connectionProvider = connectionProvider;
        this.repositoryMap = new LinkedHashMap<>();
    }

    // 為 Repository 接口創建代理實例,并注入我們自己的方法攔截器:RepositoryInvocationHandler
    @SuppressWarnings("unchecked")
    private > T getProxy(Class repositoryClass) {
        return (T) Proxy.newProxyInstance(repositoryClass.getClassLoader(),
                new Class[] {repositoryClass},
                new RepositoryInvocationHandler(this, repositoryClass));
    }

    // 獲取給定 repository 類型的代理實例
    @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;
    }

}
Repository的靈魂 RepositoryInvocationHandler

我們剛才在 RepositoryFactory.getProxy 中創建了一個RepositoryInvocationHandler實例,并傳入了RepositoryFactory實例以及代理的Repository類型。

這因為在方法攔截器中,我們需要獲取一些東西:

操作的實體類的類型,因為它的全小寫形式就是實體類所代表的表的名字

通過工廠類實例獲取一個 connection

/**
 * Created by krun on 2017/9/22.
 */
public class RepositoryInvocationHandler implements InvocationHandler {

    private RepositoryFactory factory;
    //用于保存repository的泛型信息,后面可以比較方便地獲取,雖然也可以通過 "method.getDeclaringClass()" 來獲取,但總覺得麻煩了些。
    private Class invokeRepositoryClass;

    public RepositoryInvocationHandler (RepositoryFactory factory, Class invokeRepositoryClass) {
        this.factory = factory;
        this.invokeRepositoryClass = invokeRepositoryClass;
    }

    public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName( );
        // 根據方法名選擇合適的 handle方法,以后應該是要改成表驅動,不然太多 if-else 了 ORZ
        // 說起來,表驅動的話,就有合適的地方暴露接口給用戶修改方法映射邏輯了。
        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( )));
        }

        // 這里沒有做太多考慮,暫時沒遇到問題而已...
        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( ) + "() 設置 @Query 注解");
        }
        
        // java 7的 "try-with-resource" 語法糖,挺方便的,不用操心 connection 關沒關了
        // 突然想起來,這樣寫的話好像... ConnectionProvider 就沒用了啊 ... ORZ
        try (Connection connection = factory.getConnectionProvider().provide()) {
            PreparedStatement preparedStatement = (PreparedStatement) connection
                //簡單得替換一下表名占位符
                .prepareStatement(String.format(query.value(), getEntityName()));
            // 粗暴得把參數都塞進去...
            // 以后估計要做個 switch-case 把參數類型檢查做一下
            for (int i = 1; i <= args.length; i++) {
                preparedStatement.setObject(i, args[i - 1]);
            }

            System.out.println(preparedStatement.asSql());
        
            // 把結果打出來看看
            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()" 這種情況會報錯,因為 List 是接口,無法為其創建實例
            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    計算機技術系    男    2015    軟件技術    krun    

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70527.html

相關文章

  • [JAVA][學習·練手·挖坑] 做個數據幫助雛形 · 二

    摘要:事實上,實現了接口,而也實現了接口。還記得之前說的,使用之后,其返回的實際上是一個裝飾器嗎。所以修改如下是默認全局工廠名稱,請使用別的名稱工廠已經配置完成,請不要重復配置。 這是做個數據庫幫助庫雛形 的當晚的再一次嘗試 ORZ 在意識到原來的 ConnectionProvider 提供的只是一個普通(實現了AutoCloseable接口)的 Connection,這在 Reposito...

    Eric 評論0 收藏0
  • [Java][數據工具][坑] Juice README

    摘要:注意供應器只會在倉庫工廠第一次創建工廠時調用,而參數處理器和結果解析器將在每次倉庫方法被調用時調用。解析器接收一個語句表模型的類聲明觸發解析器的倉庫方法聲明。因此當您配置了一個結果解析器,語句的執行時機將推遲到這里。 Juice 這是我自己做的一個小項目,也可能會棄坑... 留作紀念吧。GitHub 地址 簡介 Juice 是一個簡易的、尚不完善的基于 Java 的SQL數據庫工具,它...

    CoXie 評論0 收藏0
  • 全棧最后一公里 - Node.js 項目的線上服務器部署與發布

    摘要:沒有耐心閱讀的同學,可以直接前往學習全棧最后一公里。我下面會羅列一些,我自己錄制過的一些項目,或者其他的我覺得可以按照這個路線繼續深入學習的項目資源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技術軟文,閱讀需謹慎,長約 7000 字,通讀需 5 分鐘 大家好,我是 Scott,本文通過提供給大家學習的方法,...

    Nosee 評論0 收藏0
  • zhilizhili-ui 2016始動 開始做個樣例站吧 (一)

    摘要:使用,開發者用來表示異步數據流,通過操作符來查詢異步數據量,并使用來參數化異步數據流中的并發。在中,你可以表述多個異步數據流,并且使用對象訂閱事件流。因為序列是數據流,你可以使用由擴展方法實現的標準查詢操作符來查詢它們。 對 我又挖坑了 不過其實也不算挖坑 因為ui構建中就會有填坑的文章 之前一直在寫《編寫大型web頁面 結合現有前端形勢思考未來前端》這是一篇巨難寫的文章 估計要到年中...

    hzc 評論0 收藏0

發表評論

0條評論

rickchen

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<