JavaWeb中持久层最佳实践
本博客之前写过很多关于Springboot的集成各种工具类(Mybatis,邮件发送,AI接口…),其过程也是非常简单,导入依赖,在application.yml中写入配置,再在代码中添加一些注解或配置类就实现了功能的集成,这过程如此简单得益于框架的先进。在JavaWeb开发中,没有了boot的自动配置,我们在使用这些依赖就需要多那么几步(也没多多少)。
本篇讲的是持久层的最佳实践,主要就是两个:Apache DBUtils和Mybatis。前者是Apache开发的一个工具类,用于简化jdbc编程代码,加速开发周期,毕竟在早些年没有人愿意天天获取ResultSet,遍历一遍,获取对应的键(属性名)的值,最后利用构造函数得到我们需要的实体类。那样一个dao的代码量超级大,可读性也非常差。后者则是目前最流行的数据库框架,在Springboot的文章就有介绍,不赘述。
上述呢只是CRUD的过程中用到的相关工具,在这之前,还有一个比较重要的东西便是数据库连接池,顾名思义就是一个管理数据库连接的池子,它能够有效管理数据库的连接,甚至可以进行性能监控,防止安全问题出现等功能…本文将使用Hikari数据库连接池,它是目前性能最好的连接池,其他的还有例如c3p0,druid…
前言
无论是Apache的工具类,还是Mybatis框架,实际上它们在操作时都是大同小异的,至少有一点是相通的:
[!TIP]
选择语句往往会声明需要返回的实体类,这个过程中,框架或工具类(后续称
框架
)都会调用对应实体类的空构造函数,先new出一个啥内容也没有得空壳对象,然后将数据库中拿出来对应的值利用实体类的setter函数一个一个的赋值,所以你的实体类必须要有getter,setter以及空构造函数,墙裂推荐Lombok的@Data注解。 那在插入语句或更新语句也是同理,框架也会调用其setter函数去拿到要插入实体类中的属性的值。
总结就是我个人习惯在写实体类时,会打上一个@Data注解,让lombok为我隐式地生成getter和setter,然后写一个空形式参数列表的构造函数,后续有啥要用到的构造函数再慢慢过来加。
Haikri连接池
先引入依赖:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
如果我们使用Apache DButil:
书写一个数据源工厂,便于我们在不同类中拿取数据源(DataSource)。
所有数据库的信息,例如链接,用户名,密码,驱动名称都在这个类中定义,在需要的时候,因其成员变量都静态的,所以可以直接调用IHikariDataSource.getDataSource()拿取,不需要重复的new。
/**
* @author Flyinsky
* @email w2084151024@gmail.com
* @date 2024/10/9 13:13
*/
public class IHikariDataSource {
@Getter
private static com.zaxxer.hikari.HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("test");
config.setPassword("test");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setMaximumPoolSize(10);
dataSource = new com.zaxxer.hikari.HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close() {
dataSource.close();
}
}
如果我们使用Mybatis,也需要创建一个数据源工程,再在resources目录下创建mybatis-config.xml配置文件:
[!WARNING]
注意xml中的datasource标签,你的包名和类名和我的肯定不一样要自己改
/**
* @author Flyinsky
* @email w2084151024@gmail.com
* @date 2024/10/12 16:25
*/
public class HikariDataSourceFactory implements DataSourceFactory {
private Properties props;
@Override
public void setProperties(Properties props) {
this.props = props;
}
@Override
public DataSource getDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(props.getProperty("driver"));
config.setJdbcUrl(props.getProperty("url"));
config.setUsername(props.getProperty("username"));
config.setPassword(props.getProperty("password"));
//啥也不用改,自动读取xml中的信息
return new HikariDataSource(config);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="com.littlegarden.javaweb.util.HikariDataSourceFactory">
<!--你的包名和类名和我的肯定不一样要自己改-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</dataSource>
</environment>
</environments>
</configuration>
这样就成功引入了连接池,后续的CRUD都是基于连接池创建的数据源或连接上操作。
Apache DBUtils
先引入依赖:
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.8.1</version>
</dependency>
然后就可以开始书写DAO:
下面是增删改的例子,update和增类似 区别会在最后讲。
QueryRunner queryRunner = new QueryRunner(IHikariDataSource.getDataSource());//new一个工具类,传入连接池的数据源。
/**
* 根据ID查询商品
* @param id
* @return 商品
* @throws SQLException
*/
public Item getItemById(Integer id) throws SQLException {
String sql = "SELECT * FROM tb_items WHERE id = ?";//写法还是和jdbc一样,?表示待填入的查询参数
Object[] params = {id};//定义一个基类数组,按照顺序填充sql语句中的?处的参数
return queryRunner.query(sql,new BeanHandler<>(Item.class),params);//使用查询执行器执行语句,参数列表中按照sql语句,类处理器,查询参数数组的顺序填写。
}
/**
* 获取所有商品
* @return 商品列表
* @throws SQLException
*/
public List<Item> getAllItems() throws SQLException {
String sql = "SELECT * FROM tb_items";
return queryRunner.query(sql, new BeanListHandler<>(Item.class));//注意查询执行器的类处理器,如果返回的是一个集合(多条记录),需要用BeanListHandler而不是BeanHandler
}
/**
* 插入新对话列表记录
* @param chatList
* @return 新增记录的id的值
*/
public BigInteger generateNewChatList(ChatList chatList) throws SQLException {
String sql = "INSERT INTO tb_chatlist (userId, title, appId) VALUES (?,?,?)";
Object[] params = {chatList.getUserId(),chatList.getTitle(),chatList.getAppId()};
return queryRunner.insert(sql,new ScalarHandler<>(),params);
}
//insert方法返回主键自增的id的值(如果有),而update返回影响的记录的行数