Spring JdbcTemplate、JPA 和许多其他 ORM 框架的非常简单和高性能的替代品

今天同事问有没有简单轻量的数据库操作库,我向他推荐了sql2o,可以在小项目中轻松使用。

sql2o参考文档

Sql2o 是一个小型 Java 库,它可以轻松地针对您的 JDBC 兼容数据库执行 sql 语句。

Sql2o 是 Spring JdbcTemplate、JPA 和许多其他 ORM 框架的非常简单和高性能的替代品。如上所示,它还与最流行的依赖注入框架之一完美融合。

配置

一需要的配置是数据库的连接 url、用户名和密码。此信息在 Sql2o 类的构造函数中指定。在下面的示例中,将 sql2o 设置为连接到本地 mysql 数据库。

Sql2o sql2o = new Sql2o("jdbc:mysql://localhost:3306/myDB", "myUsername", "topSecretPassword");

或者可以指定一个数据源。

Sql2o sql2o = new Sql2o(myDataSource);

通常只需要初始化一次

public class myDao {

    private Sql2o sql2o;

    public myDao() {

        this.sql2o = new Sql2o("jdbc:mysql://localhost:3306/myDB", "myUsername", "topSecretPassword");

    }

}

程序启动时创建一次

public class MyDao {

    private static Sql2o sql2o;

    static{

        sql2o = new Sql2o("jdbc:mysql://localhost:3306/myDB", "myUsername", "topSecretPassword");

    }

}

与 Spring 框架集成

<dependency>

<groupId>org.sql2o</groupId>

<artifactId>sql2o</artifactId>

<version>1.6.0</version>

</dependency>

Sql2o 实例是线程安全的。这意味着 Sql2o 可以在 Spring 运行时配置为单例组件。无论您使用的是基于 XML 还是基于 Java 的应用程序上下文,都可以轻松地将 Sql2o 添加到 Spring 上下文中。

基于 Javaconfig 的配置

@Configuration@EnableTransactionManagementpublic class DatabaseContext implements TransactionManagementConfigurer {

   @Bean

   public DataSource dataSource() {

      final BasicDataSource dataSource = new BasicDataSource();

      dataSource.setDriverClassName("com.mysql.jdbc.Driver");

      dataSource.setUrl("jdbc:mysql://localhost:3306/testDB");

      dataSource.setUsername("user");

      dataSource.setPassword("pass");

   }

   @Bean

   @Override

   public PlatformTransactionManager annotationDrivenTransactionManager() {

      return new DataSourceTransactionManager(dataSource());

   }

   @Bean

   public Sql2o sql2o() {

      return new Sql2o(dataSource());

   }

}

springboot 配置

@Configuration

public class myConfiguration {

@Autowired

private DataSource dataSource;

@Bean

    public Sql2o sql2o(){

        return new Sql2o(dataSource);

    }

//使用方式伪代码

public void test(){

String sql = "SELECT count(id) FROM t_user";

        try (Connection con = sql2o().open()) {

            int count =  con.createQuery(sql).executeScalar(Integer.class);

            System.out.println(count);

        }}

}

从数据库中获取数据

当我们的 sql2o 实例在我们的 DAO 类中设置好后,我们就可以运行一些查询了。我们要做的第一件事是从数据库中获取一些数据。Sql2o 将数据解析为 POJO 对象

public class Task {

    private Long id;

    private String description;

    private Date dueDate;

    // getters and setters here

}

public List<Task> getAllTasks(){

    String sql =

        "SELECT id, description, duedate " +

        "FROM tasks";

    try(Connection con = sql2o.open()) {

        return con.createQuery(sql).executeAndFetch(Task.class);

    }

}

public List<Task> getTasksBetweenDates(Date fromDate, Date toDate){

    String sql =

        "SELECT id, description, duedate " +

        "FROM tasks " +

        "WHERE duedate >= :fromDate AND duedate < :toDate";

    try(Connection con = sql2o.open()) {

        return con.createQuery(sql)

            .addParameter("fromDate", fromDate)

            .addParameter("toDate", toDate)

            .executeAndFetch(Task.class);

    }

}

简单类型映射

public Integer getStudentCount(){

    String sql = "SELECT count(id) FROM students";

    try (Connection con = sql2o.open()) {

        return con.createQuery(sql).executeScalar(Integer.class);

    }

}

public List<Integer> getStudentIdList(){

    String sql = "SELECT id FROM students";

    try (Connection con = sql2o.open()) {

        return con.createQuery(sql).executeScalarList(Integer.class);

    }   

}

将数据库结果作为 Map 对象列表返回。在这种情况下,List 的每个元素都是一个表示虚拟记录。

public List<Map<String,Object>> getReportData(){

    String complexSql = "...";

    try (Connection con = sql2o.open()) {

        return con.createQuery(complexSql).executeAndFetchTable().asList();

    }

}

加载

如果需要读取大量数据,使用 executeAndFetch() 可能会遇到内存问题。可以使用 executeAndFetchLazy() 遍历 ResultSet 并确保不会耗尽内存。

例如,假设数据库中有数百万个任务,并且希望分批读取 1,000 个任务,并在执行过程中将每个批次刷新到一个文件中:

public void readAndFlushAllTasks() {

    String sql = "SELECT id, description, duedate  FROM tasks";

    final int BATCH_SIZE = 1000;

    List<Task> batch = new ArrayList<Task>(BATCH_SIZE);

    try (Connection con = sql2o.open()) {

        try (ResultSetIterable<Task> tasks = con.createQuery(sql).executeAndFetchLazy(Task.class)) {

            for (Task task : tasks) {

                if (batch.size() == BATCH_SIZE) {

                    // here is where you flush your batch to file

                    batch.clear();

                }

                batch.add(task);

            }

            if (!batch.isEmpty()) {

                // also flush to your file the last items

                batch.clear();

            }

        }

    }

}

注意:ResultSetIterable 类是可自动关闭的,因此您可以将其包装在 try-with-resource 语句中。这可确保在您完成后关闭基础 ResultSet。

列映射

在 sql 查询中使用别名,如下例所示。

public List<Task> getTasksBetweenDates(Date fromDate, Date toDate){

    String sql =

        "SELECT id, description, due_date duedate " +

        "FROM tasks " +

        "WHERE duedate > :fromDate AND duedate < :toDate";

    try (Connection con = sql2o.open()) {

        return con.createQuery(sql)

            .addParameter("fromDate", fromDate)

            .addParameter("toDate", toDate)

            .executeAndFetch(Task.class);

    }

}

另一种方法是将列映射添加到 sql2o 查询。这可以通过调用 addColumnMapping 方法来完成,如下所示。

public List<Task> getTasksBetweenDates(Date fromDate, Date toDate){

    String sql =

        "SELECT id, description, due_date " +

        "FROM tasks " +

        "WHERE due_date > :fromDate AND due_date < :toDate";

    try (Connection con = sql2o.open()) {

        return con.createQuery(sql)

            .addParameter("fromDate", fromDate).addParameter("toDate", toDate)

            .addColumnMapping("DUE_DATE", "dueDate")

            .executeAndFetch(Task.class);

    }

}

映射公共的列

Map<String, String> colMaps = new HashMap<String,String>();

colMaps.put("DUE_DATE", "dueDate");colMaps.put("DESC", "description");colMaps.put("E_MAIL", "email");colMaps.put("SHORT_DESC", "shortDescription");

sql2o.setDefaultColumnMappings(colMaps);

更新和插入

使用 sql2o 更新和插入是使用 executeUpdate() 方法执行的。

更新示例

String updateSql = "update myTable set value = :valParam where id = :idParam";

try (Connection con = sql2o.open()) {

    con.createQuery(updateSql)

    .addParameter("valParam", foo)

    .addParameter("idParam", bar)

    .executeUpdate();

}

插入示例:

String insertSql =

"insert into myTable(id, value) values (:idParam, :valParam)";

try (Connection con = sql2o.open()) {

    con.createQuery(insertSql)

    .addParameter("idParam", bar)

    .addParameter("valParam", foo)

    .executeUpdate();

}

获取插入id

通过 createQuery 方法的重载来完成的:createQuery(String sql, boolean returnGeneratedKeys)要获取实际插入的值,请在执行语句后调用 getKey() 方法。

String sql = "insert into MYTABLE ( value ) values ( :val )";

try (Connection con = sql2o.open()) {

    int insertedId = con.createQuery(sql, true)

    .addParameter("val", someValue)

    .executeUpdate()

    .getKey();

}

bind()方法。

如果需要从 POJO 类中添加许多参数,可以使用 Query.bind(Object) 方法。

public class MyModel {

private int prop1;

private String prop2;

private String prop3;

private Date prop4;

// and so on..

// Getters and settes

}

为参数指定与模型类中相应属性相同的名称

String sql =

"insert into MYTABLE(col1, col2, col3, col4 ...) " +

"values (:prop1, :prop2, :prop3, :prop4 ...)";

try (Connection con = sql2o.open()) {

    con.createQuery(sql).bind(model).executeUpdate();

}

事务

可以通过调用Sql2o实例上的beginTransaction()方法打开一个事务;这将返回一个 Connection 实例。使用返回的 Connection 对象的 createQuery() 方法创建的所有查询都在事务中运行。调用 commit() 或 rollback() 来提交或回滚事务并关闭连接。

String sql1 = "INSERT INTO SomeTable(id, value) VALUES (:id, :value)";String sql2 = "UPDATE SomeOtherTable SET value = :val WHERE id = :id";

try (Connection con = sql2o.beginTransaction()) {

    con.createQuery(sql1).addParameter("id", idVariable1).addParameter("val", valueVariable1).executeUpdate();

    con.createQuery(sql2).addParameter("id", idVariable2).addParameter("val", valueVariable2).executeUpdate();

    con.commit();

}

注意:如果你没有在 Connection 对象上显式调用 commit() 或 rollback(),事务将在退出 try-with-resources 块时自动回滚。

批量运行查询

如果您需要使用不同的参数多次运行 UPDATE、INSERT 或 DELETE 查询,则通过批量运行它们将获得巨大的性能提升。

public void insertABunchOfRows(){

    final String sql = "INSERT INTO SomeTable(id, value) VALUES (:id, :value)";

    try (Connection con = sql2o.beginTransaction()) {

        Query query = con.createQuery(sql);

        for (int i = 0; i < 100; i++){

            query.addParameter("id", i).addParameter("value", "foo" + i).addToBatch();

        }

        query.executeBatch(); // executes entire batch

        con.commit();

    }

}

注意应该在事务中批量处理

注册自定义转换器

实现和使用自定义转换器

示例:Java 8 LocalDate  java.sql.Date 转换

首先实现 org.sql2o.converters.Converter 接口

public class LocalDateConverter implements Converter<LocalDate> {

        @Override

        public LocalDate convert(final Object val) throws ConverterException {

            if (val instanceof java.sql.Date) {

                return ((java.sql.Date) val).toLocalDate();

            } else {

                return null;

            }

        }

        @Override

        public Object toDatabaseParam(final LocalDate val) {

            if (val == null) {

                return null;

            } else {

                return new java.sql.Date(val.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());

            }

        }

    }

在实例化时注册

final Quirks quirks = new NoQuirks(){

    {

        converters.put(java.sql.Date.class, new LocalDateConverter ());

    }

};

final DataSource dataSource = //final

Sql2o sql2o = new Sql2o(dataSource, quirks);

一、SBORM 介绍 1、目前只考虑支持 mysql; 2、基于spring jdbc的上层封装,底层jdbc操作基于JdbcTemplate,对于使用spring jdbc的人会有一点价值,比较简洁的封装可以节省很多重复劳动,具体节省多少可以看看example; 3、实现一套简单的ORM(直接使用spring rowmapper,insert自己实现),可以基于对象进行crud和相对复杂(感觉比hibernate强大一点)的sql操作; 4、基于对象指定查询的字段,大部分时候可以忘掉表结构进行业务开发; 5、支持简单的数据库路由,读写分离(半自动,需要指定取writer还是reader,默认规则reader采用随机的方式,当然也可以手动指定); 6、支持简单的分表,主要是针对一定规则的分表,比如百分表、千分表,也可以自己指定分表后缀; 7、简单的单表查询(比如所有条件是and或者or结构),基本实现0sql代码编写(类似HibernateTemplate selectByExample、findByCriteria、find等方法); 8、简单的单表排序支持,支持多个排序条件组合; 9、对于复杂的sql查询,提供获取jdbctemplate实例进行操作,类似spring jdbc的常规用法; 10、提供Entity代码生成接口,Entity并非简单的pojo(尽可能不要去修改此类),引入字段常量类,方便查询的时候指定选择字段,从而更好实现查询条件的封装; 二、为什么写SBORM? 1、hibernate:过于臃肿,使用不够灵活,优化难(其实主要是因为很少用),HQL感觉就是个渣,在 mysql几乎一统天下的背景下,跨数据库级别的兼容吃力不讨好。Hibernate的对象化关联处理确实挺强大,但是使用起来坑太多,有多少人敢在项目 中大范围使用真不知道,屠龙刀不是人人都提的起啊。 2、mybatis:轻量级,基于xml的模式感觉不利于封装,代码量不小,基于xml维护也麻烦(个人观点, 现在注解模式貌似也挺不错),感觉mybatis更适合存在dba角色的年代,可以远离代码进行sql调优,复杂的查询拼装起来也更加优雅(java基本 就是if else ...),但是对于查询业务简单但是数据库集群环境的场景有点憋屈(其实对mybatis使用也不多,瞎评论^_^)。 3、spring jdbc:小巧,灵活,足够优秀,个人比较喜欢使用,但是代码量偏大,原生的接口重复劳动量大,比如insert、mapper之类的; SBORM只是针对spring jdbc的一些不方便的地方,做了一些封装,更加简化日常的开发工作,基于spring jdbc的RowMapper自动实现对象映射,也勉强算的上叫ORM,只是大部分功能已经由spring jdbc实现了。 平时不太喜欢使用hibernate和mybatis,主要是使用spring jdbc,写这个东西的出发点主要是平时使用spring jdbc觉 得比较麻烦,重复性的代码偏多,一方面通过自动mapper降低返回结果处理工作量,另一方面参考hibernate对象化查询条件的模式,写了一个 QueryBudiler,使得更多简单的单表查询可以通过对象组织查询、更改逻辑,避免过多去写相似性的SQL语句,减少DAO接口量。 三、一些亮点 1、Entity的设计:很多人看了也许会说,这个不是POJO,不是纯粹的Java Bean,显得很另类。但是有多人在开发过程中(特别是在写sql的时候),经常要去看看表结构设计?还有多少次因为改了表某个字段,还得遍历去查找哪些 sql使用了这个字段?多少次看到在代码中直接传入字段名作为查询参数感到别扭?如果将表结构字段都用java对象去描述,能够解决这些问题,就不必要在 乎是不是POJO了,后面看example的时候应该能体会这么做的一些好处,至少我觉得是挺方便的,将大部分查询脱离表结构设计。 2、简单的数据库路由:如果分库结构不是太复杂(比如简单的读写分离、或者多个库集成),BaseDao可以自 动进行路由(比如读写分离,根据业务模式指定读、写库),如果非默认的路由规则,也可以通过手动设置的模式,进行数据库路由。数据库路由直接由 Entity指定,所有的路由都是根据Entity识别,也就是说查询也是围绕Entity展开的,避免类似使用spring jdbc的时候,各种 template实例跳来跳去,硬编码引入,写一个业务还得看看到底该用哪个template,尤其是多个数据库共用一个template实例的时候。 3、QueryBuilder:单表查询基本上都可以实现零Sql(除非查询条件特别复杂的),更新、删除等操作也可以通过QueryBuilder进行批量处理,不局限于根据主键来处理。 4、分表操作的支持:对于分表操作和常规的使用没有区别,只是指定分表规则,mybatis好像也可以通过制定参数实现分表处理,没搞清楚hibernate对这个是怎么处理的(hibernate好像是bean和表一对一绑定的)? 标签:sborm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值