个人读书笔记,题外话都写在第1章https://blog.csdn.net/u013652550/article/details/119800308。
第5章主要介绍了JDBC的使用以及Spring DAO模块对JDBC的封装。由于阅读本书时默认已学完Java基础(包括JDBC),对JDBC的使用不会过多赘述。
5.1 JDBC详解
使用IDEA创建Spring Initialzr项目时点击下一步,记得勾选上SQL支持。pom.xml中应该会自动引入驱动,也可手动补充。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
com.mysql.cj.jdbc.Driver
这里注册驱动使用新的类代替com.mysql.jdbc.Driver。
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
URL的若干注意事项
使用&分割参数;记得增加characterEncoding=UTF-8,以及useSSL=true等。
PreparedStatement
使用方法基本等同于Statement,区别在于SQL语句中参数值使用?代替,执行前使用setter方法设置。这里构造方法的额外参数指明返回自增主键。
String sql="insert into t_user (name, age, money) values (?, ?, ?)";
prestmt=conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
prestmt.setString(1, "小李");
prestmt.setInt(2, 25);
prestmt.setDouble(3, 222.22);
prestmt.executeUpdate();
ResultSet rs=prestmt.getGeneratedKeys();
int ID = rs.getInt(1);
同时,注意到自己以前编程时的不足:书上示例中查询完之后都会释放资源,通过在try-catch语句块后增加finally语句块。
finally {
if (conn != null) {conn.close();}
if (prestmt != null) {prestmt.close();}
if (rs != null) {rs.close();}
}
CallableStatement
由于我现在(大一暑假)并未学完数据库课程,之前Java基础中也并未学习该类,对于书上所说的存储过程的概念摸索了很久。大致的含义就是调用数据库中的某个函数,CallableStatement提供了方法将参数传入,以及获取传出参数。SQL中也有函数的概念,即存储过程,不同于编程语言函数中的返回值,它通过传出参数返回。下面贴上书上SQL源码:
CREATE PROCEDURE `p_selectUserByAge` (age int, out count int)
BEGIN
set count = (select count(1) from t_user t where t.age = age);
select * from t_user t where t.age = age;
END
使用类似PreparedStatement的方法创建CallableStatement对象,使用registerOutParameter设置传出参数,执行完毕后使用getter方法获取。
callstmt = conn.prepareCall("{call p_selectUserByAge(?, ?)}");
callstmt.setInt(1, 25);
callstmt.registerOutParameter(2, Types.INTEGER);
rs = callstmt.executeQuery();
int respond = callstmt.getInt(2);
批处理
Statement和PreparedStatement都是通过addBatch()方法添加一条SQL语句,通过executeBatch()方法批量执行SQL语句,返回一个int类型的数组。其中PreparedStatement支持带参数的不完整SQL。
事务处理
Connectioni实例在执行前调用setAutoCommit(false)开启事务,在try-catch语句块中对异常调用rollback()进行事务回滚。
5.2 Spring DAO模块
配置
DAO模块依赖commons-dbcp.jar、commons-pool.jar。在pom.xml中进行配置。同样如果代码标红记得点右上角“加载Maven变更”(PS:实测注入最后一项mysql-connector-java后运行时会报错,不注入即可)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
使用JdbcDaoSupport:在DAO类声明处加上extends JdbcDaoSupport即可使用getJdbcTemplate()方法,简化JDBC的使用。
在XML中需配置一系列数据库相关属性,最后在顶层beans标签中还要设置自动装填属性(摸索了一天才解决了bug):beans default-autowire="byName"。(个人猜测是因为这里被注入属性的bean是已有的)
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/daodemodb?characterEncoding=utf-8&useSSL=true"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="userDao" class="com.demo.model.dao.DaoApplication" depends-on="dataSource">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
MappingSqlQuery
配置完后,在DAO类中直接调用getJdbcTemplate().queryForList(sql)即可轻松执行DQL。然而该方法返回的是List<Map<String, Object>>类型(看不懂自行去补习Java泛型与集合框架),需自行补充方法遍历结果集转换成Java对象。更好的解决方法是继承抽象类extends MappingSqlQuery<T>,实现其抽象方法protected T mapRow(ResultSet rs, int rowNum) throws SQLException。还是贴一段源码吧:(这里代码与书上不同)
public class UserMappingSqlQuery extends MappingSqlQuery<User> {
public static UserMappingSqlQuery builder() {
return new UserMappingSqlQuery();
}
public UserMappingSqlQuery DataSource(DataSource dataSource) {
setDataSource(dataSource);
return this;
}
public UserMappingSqlQuery Sql(String sql) {
setSql(sql);
return this;
}
public UserMappingSqlQuery Parameters(SqlParameter parameter) {
setParameters(parameter);
return this;
}
@Override
protected User mapRow(ResultSet rs, int rowNum) throws SQLException {
return User.builder()
.Id(rs.getInt("id"))
.Name(rs.getString("name"))
.Age(rs.getInt("age"))
.Money(rs.getDouble("money"));
}
}
调用示例:
public List<User> QueryAllUser() {
String sql = "select * from t_user where ?";
UserMappingSqlQuery userQuery = UserMappingSqlQuery.builder()
.DataSource(getDataSource())
.Sql(sql)
.Parameters(new SqlParameter(Types.VARCHAR));
userQuery.compile();
return userQuery.execute(new Object[]{"1=1"});
}
说明:相比起书上的一排xxx.setter方法,这里可以根据我的源码学习一下builder()的使用,实质就是setter方法返回对象自身this。调用时先初始化MappingSqlQuery的子类,在执行compile()编译,最后执行execute(Object...arg)执行(可变参数,实际上不需要如示例代码中的Object数组)。where 1=1是为了查询所有结果,sql可能会给出警告。通过实现mapRow方法,现在可以直接返回List<T>类型。
SqlUpdate的使用
继承SqlUpdate类,在构造方法中进行初始化,声明参数类型,对外接口的方法中调用update(Object ... args)方法即可(同样是可变参数,填写SQL语句中?对应数量参数即可)。贴一段构造方法:
public XXX(DataSource ds) {
setDataSource(ds);
setSql("update t_user set money = ? where name = ?");
declareParameter(new SqlParameter(java.sql.Types.DOUBLE));
declareParameter(new SqlParameter(java.sql.Types.VARCHAR));
compile();
}
在XML中配置使用构造函数注入即可。亦可外部调用时使用BeanFactory的实例方法getBean("dataSource")获取。
<constructor-arg ref="dataSource" index="0"/>
SqlFunction的使用
默认返回int,也可以重载返回其他类型。直接使用构造函数初始化即可使用。
SqlFunction sf = new SqlFunction(dataSource, "select count(1) from t_user");
sf.compile();
int count = sf.run();
5.3 Spring事务管理
在XML中进行配置。同样记得设置beans的default-autowire属性。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
以下为配置业务层代理,通过在需要回滚的方法前添加@Transactional注解也可实现。(默认回滚运行时异常,可以设置参数rollbackFor = Exception.class。详细配置可以参考Spring Boot 中使用 @Transactional 注解配置事务管理_皂白-CSDN博客_@transactional
<bean id="..." class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="..."/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="...">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
其中prop的key属性省略项可以使用类似正则表达式,如DAO*表示以DAO开头的所有方法。PROPAGATION_REQUIRED表示加入当前事务或新建事务(如果没有)。(不理解的话想一下函数嵌套调用,事务也是可以嵌套的)。关于propagation参数的具体配置可以参考透彻的掌握 Spring 中@transactional 的使用 - 茄子_2008 - 博客园 (cnblogs.com)
需要注意下面三种 propagation 可以不启动事务。本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。