1. 概述

在这个简短教程中,我们将介绍最常见的NonTransientDataAccessException类型的几种,并通过示例进行说明。

2. 基础异常类

这个主要异常类的子类表示与数据访问相关的非临时或永久性异常。

简单来说,这意味着只要根本原因未被修复,导致异常的方法的所有未来尝试都将失败。

3. DataIntegrityViolationException

这种NonTransientDataAccessException的子类型在尝试修改数据时违反完整性约束时抛出。

例如,在Foo类中,name列被定义为不允许null值:

@Column(nullable = false)
private String name;

如果我们尝试保存一个没有设置名称的实例,可以预期会抛出DataIntegrityViolationException

@Test(expected = DataIntegrityViolationException.class)
public void whenSavingNullValue_thenDataIntegrityException() {
    Foo fooEntity = new Foo();
    fooService.create(fooEntity);
}

*3.1. DuplicateKeyException

DataIntegrityViolationException的子类之一是DuplicateKeyException,当尝试保存具有已存在的主键或唯一约束列(如foo表中id为1的两行)中已存在的值时,会抛出此异常:

@Test(expected = DuplicateKeyException.class)
public void whenSavingDuplicateKeyValues_thenDuplicateKeyException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);
    jdbcTemplate.execute("insert into foo(id,name) values (1,'a')");
    jdbcTemplate.execute("insert into foo(id,name) values (1,'b')");
}

*4. DataRetrievalFailureException

当在检索数据过程中出现问题时,如查找数据库中不存在标识符的对象时,会抛出此异常。

例如,我们将使用JdbcTemplate类,它有一个会抛出此异常的方法:

@Test(expected = DataRetrievalFailureException.class)
public void whenRetrievingNonExistentValue_thenDataRetrievalException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);
    
    jdbcTemplate.queryForObject("select * from foo where id = 3", Integer.class);
}

*4.1. IncorrectResultSetColumnCountException

尝试从表中获取多列数据但未创建适当RowMapper时,会抛出此子类异常:

@Test(expected = IncorrectResultSetColumnCountException.class)
public void whenRetrievingMultipleColumns_thenIncorrectResultSetColumnCountException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);

    jdbcTemplate.execute("insert into foo(id,name) values (1,'a')");
    jdbcTemplate.queryForList("select id,name from foo where id=1", Foo.class);
}

*4.2. IncorrectResultSizeDataAccessException

当检索到的记录数量与预期不符时,例如预期获取一个Integer值,但查询返回两行时,会抛出此异常:

@Test(expected = IncorrectResultSizeDataAccessException.class)
public void whenRetrievingMultipleValues_thenIncorrectResultSizeException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);

    jdbcTemplate.execute("insert into foo(name) values ('a')");
    jdbcTemplate.execute("insert into foo(name) values ('a')");

    jdbcTemplate.queryForObject("select id from foo where name='a'", Integer.class);
}

*5. DataSourceLookupFailureException

当指定的数据源无法获取时,会抛出此异常。例如,我们将使用JndiDataSourceLookup类来查找不存在的数据源:

@Test(expected = DataSourceLookupFailureException.class)
public void whenLookupNonExistentDataSource_thenDataSourceLookupFailureException() {
    JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
    dsLookup.setResourceRef(true);
    DataSource dataSource = dsLookup.getDataSource("java:comp/env/jdbc/example_db");
}

*6. InvalidDataAccessResourceUsageException

当资源被错误地访问时,会抛出此异常,例如用户缺乏SELECT权限。

为了测试此异常,我们需要撤销用户的SELECT权限,然后运行一个SELECT查询:

@Test(expected = InvalidDataAccessResourceUsageException.class)
public void whenRetrievingDataUserNoSelectRights_thenInvalidResourceUsageException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);
    jdbcTemplate.execute("revoke select from tutorialuser");

    try {
        fooService.findAll();
    } finally {
        jdbcTemplate.execute("grant select to tutorialuser");
    }
}

注意,我们在finally块中恢复了用户的权限。

*6.1. BadSqlGrammarException

InvalidDataAccessResourceUsageException的一个常见子类是BadSqlGrammarException,当尝试使用无效SQL执行查询时抛出:

@Test(expected = BadSqlGrammarException.class)
public void whenIncorrectSql_thenBadSqlGrammarException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);
    jdbcTemplate.queryForObject("select * fro foo where id=3", Integer.class);
}

当然,可以看到查询中的fro部分是无效的。

*7. CannotGetJdbcConnectionException

当通过JDBC进行连接尝试失败时,会抛出此异常,例如当数据库URL不正确时。如果我们像这样写URL:

jdbc.url=jdbc:mysql:3306://localhost/spring_hibernate5_exceptions?createDatabaseIfNotExist=true

在尝试执行语句时,将抛出CannotGetJdbcConnectionException

@Test(expected = CannotGetJdbcConnectionException.class)
public void whenJdbcUrlIncorrect_thenCannotGetJdbcConnectionException() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(restDataSource);
    jdbcTemplate.execute("select * from foo");
}

8. 总结

在这篇直截了当的教程中,我们了解了一些NonTransientDataAccessException类的常见子类型。

所有示例的实现可以在GitHub项目中找到。当然,所有示例都使用内存数据库,因此您可以轻松运行而无需设置任何环境。