《Spring快速入门》读书笔记 第5章 Spring之DAO

        个人读书笔记,题外话都写在第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&amp;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,事务将不会发生回滚。

  1. TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值