摘要:前者是数据库驱动,由于这是个挖坑性质的东西,所以只针对做功能了后者是代码生成框架,挺好用的,强烈推荐也就是说,并不使用常见的数据库连接池,比如。的工厂已经被初始化了,不能再对其进行配置。
在以往的编码中,使用过 spring-data-jpa,也用过 hibernate 和 mybatis。在简单的数据库操作中,spring-data-jpa 是用起来最爽的,毕竟在 IntelliJ IDEA 中可以获得如下体验:
瞧瞧,实体类属性推导,查询条件推导。声明完接口就可以用了,一行sql都不用敲,多爽 : P
在这里就不讨论这三个框架的优劣了,毕竟就我目前的使用场景而言,也体会不太出来到底谁好用...毕竟复杂的
SQL查询都是要 类似hql或者XML 的解决方案来做的。
本着挖坑学习的精神,今天开始会试着一步一步做出一个自己的数据库帮助库 (不敢叫框架,毕竟#行业标准里太多 feature,实力不够,做不来 ORZ).
今天就做个雏形吧,雏形的意思就是:看起来好像完成了一些功能,但只是实验性得编码 : P
说明这个帮助库就命名为 ice 吧,请原谅 起名字困难症 ORZ
配置 Configuration这是一个 笔记 类型的文章,所有可能会有一些 啊 写到这里才想起来 这样的情况...
本文只引用 mysql-connecter 和 lombok 这两个包。
前者是数据库驱动,由于这是个挖坑性质的东西,所以只针对 MYSQL 做功能了;
后者是代码生成框架,挺好用的,强烈推荐也就是说, ice 并不使用常见的数据库连接池,比如 druid、cp30。而是自己实现一个缓存连接获取器,毕竟挖坑就挖深点嘛哈哈。
本文假定读者具备一定的 Java 能力,比如 反射、代理 这两个点,有兴趣可以看看我之前的文章。
用过前边所说的三个框架的同学肯定配过配置文件对吧,我一般配合 spring-boot 使用 spring-data-jpa,所以在 application.properties 配置;其他两个框架则是在传统的 SSH、SSM 环境下配置 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
那么结合一下,现在的模板应该是这样的:
/** * Created by krun on 2017/9/22. */ public interface RepositoryRepository工厂 RepositoryFactory{ @Query("SELECT * FROM %s") List findAll(); ... }
现在用户可以继承 Repository 接口来声明一个指定实体类的 repository,我们需要一个工厂类来为这些接口类创建代理对象(Proxy)以注入我们的方法拦截器。
/** * Created by krun on 2017/9/22. */ public class RepositoryFactory { //全局工厂的名字 private static final String GLOBAL_FACTORY = "GLOBAL"; //用来保存给定名称和其对应的工厂实例 private static final LinkedHashMapRepository的灵魂 RepositoryInvocationHandlerfactoryMap; 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; } }
我们刚才在 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 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( ); // 根据方法名选择合适的 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
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/70527.html
摘要:事实上,实现了接口,而也实现了接口。还记得之前说的,使用之后,其返回的实际上是一个装饰器吗。所以修改如下是默认全局工厂名称,请使用别的名称工厂已经配置完成,请不要重复配置。 这是做个数据库帮助库雏形 的当晚的再一次尝试 ORZ 在意识到原来的 ConnectionProvider 提供的只是一个普通(实现了AutoCloseable接口)的 Connection,这在 Reposito...
摘要:注意供应器只会在仓库工厂第一次创建工厂时调用,而参数处理器和结果解析器将在每次仓库方法被调用时调用。解析器接收一个语句表模型的类声明触发解析器的仓库方法声明。因此当您配置了一个结果解析器,语句的执行时机将推迟到这里。 Juice 这是我自己做的一个小项目,也可能会弃坑... 留作纪念吧。GitHub 地址 简介 Juice 是一个简易的、尚不完善的基于 Java 的SQL数据库工具,它...
摘要:没有耐心阅读的同学,可以直接前往学习全栈最后一公里。我下面会罗列一些,我自己录制过的一些项目,或者其他的我觉得可以按照这个路线继续深入学习的项目资源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技术软文,阅读需谨慎,长约 7000 字,通读需 5 分钟 大家好,我是 Scott,本文通过提供给大家学习的方法,...
摘要:使用,开发者用来表示异步数据流,通过操作符来查询异步数据量,并使用来参数化异步数据流中的并发。在中,你可以表述多个异步数据流,并且使用对象订阅事件流。因为序列是数据流,你可以使用由扩展方法实现的标准查询操作符来查询它们。 对 我又挖坑了 不过其实也不算挖坑 因为ui构建中就会有填坑的文章 之前一直在写《编写大型web页面 结合现有前端形势思考未来前端》这是一篇巨难写的文章 估计要到年中...
阅读 3650·2021-09-22 15:15
阅读 3555·2021-08-12 13:24
阅读 1309·2019-08-30 15:53
阅读 1816·2019-08-30 15:43
阅读 1178·2019-08-29 17:04
阅读 2792·2019-08-29 15:08
阅读 1573·2019-08-29 13:13
阅读 3084·2019-08-29 11:06