Spring Data JPA之CRUD
事务管理
在数据库操作中添加数据的时候,是需要使用到事务来进行管理的,而在上面的添加User数据的示例中,并没有看到在哪里使用了事务管理,那么它是怎么起作用的呢???
需要知道的是,如果没有事务,我们对数据表的增删改操作不可能会成功的。那么问题就来了,SpringData 是在哪里进行了事务的管理呢??
在示例中,通过userDao对象调用了save()方法,这个方法是由CrudRepository接口提供的,而接口,只定义了一个方法主体,并没有具体的实现,那么问题又来了,它是怎么实体数据的保存的???
这就要来看SimpleJpaRepository类了:
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {}
public interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {}
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID>{}
从上表可以看到,SimpleJpaRepository是CrudRepository接口的间接实现类,而在SimpleJpaRepository类上,使用了@Transactional(readOnly = true)注解,在类上使用@Transactional注解,表示这个类中所有的方法都开启事务管理,readOnly = true的意思是,所有的事务,都是只读模式的,只读,意味着增删改无法正常使用到事务。
再来看save()方法的源码:
@Transactional
public <S extends T> S save(S entity) {
......
}
在save()方法方法上,还是使用了@Transactional,这表示当前的方法正常开启事务的应用,有了这个注解,在类上标注的事务功能就对save()方法没有效果了,此时就不再是只读了,而是可以正常使用的增删改事务管理。
是的,在Spring中,事务的管理,可以使用注解@Transactional来实现。 而对于事务,@Transactional这个注解只会在RuntimeException(运行时异常),这种异常时才会进行数据回滚,而Exception(受检异常)抛出的时候,是不会进行数据回滚的,这个时候我们想他报Exception异常的时候依旧想他进行数据回滚要怎么办。只需要在@Transactional后面加上(rollbackFor = Exception.class),就行了@Transactional(rollbackFor = Exception.class)。
同样的,删除delete()方法也是使用了这个注解。
同一个方法中多个数据库操作,使用注解之后,就变成一笔业务,由于事务的原子性和一致性,要么都成功,要么就一起不成功!!
删除数据
接下来测试一下,使用SpringData JPA如何删除数据。在测试类中添加一个删除数据的方法:
@Test
void deleteUserTest() {
userDao.deleteById(3);
}
测试结果: 删除uid=3的User数据的操作!
Hibernate: select user0_.uid as uid1_0_0_, user0_.birthday as birthday2_0_0_, user0_.password as password3_0_0_, user0_.u_sex as u_sex4_0_0_, user0_.username as username5_0_0_ from t_user user0_ where user0_.uid=?
Hibernate: delete from t_user where uid=?
这两条SQL语句,是由Hibernate框架自动生成的,在查询数据的时候,会自动使用as关键字来进行别名的设置。 测试是成功了,从控制台给出的SQL操作语句来看,先进行了查询是否存在指定的主键,如果有就进行删除。如果没有,就会报异常:
org.springframework.dao.EmptyResultDataAccessException: No class com.edu118.entity.User entity with id 3 exists!
修改数据
新增和删除都测试过后,现在来看一下,修改用户数据要如何来实现。
在SpringData的应用中,如果要修改数据,使用的是save()方法,很奇怪吧。看源码就知道了:
在save()方法中,通过isNew()方法来判断要保存的实体类是否已经存在,,如果不存在就使用persist()保存,如果存在了并且有主键值,就使用merge()方法进行修改保存。
知道了方式,现在就来进行数据的修改吧,在测试类中定义一个修改的方法:
测试之后,发现是添加新的用户数据。修改密码:
如果数据设置上了id,也就是主键值,那么,会先进行主键的查询,如果有数据,那么就做修改保存。
修改数据的时候,是所有的属性都参与修改操作。
查询单条数据
增删改都完成之后,就是数据的查询了,先来以主键进行查询。 测试方法:
从代码中可以看到,要查询指定主键值的数据,可以使用findById()方法来完成,传入要查询的主键值就可以了,但是,查询的结果却是返回Optional对象。
Optional 类主要解决的问题是臭名昭著的空指针异常,从 Java 8 引入的一个很有趣的特性是 Optional 类。本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
另外,如果是查询单条数据,还可以使用另外两个方法:
findOne(Example<S> example):
根据条件查询单条数据,在早期版本,是直接通过主键查询的,在新版本中,更新了功能,可以自定义查询的条件。Example就是查询条件的封装类,通过它的静态方法of(),来接收一个实体类对象,根据这个对象非null的属性来确定查询的条件。
//以主键查询数据
@Test
void findUserById2() {
User user = new User();
user.setUid(2);
user.setUsername("123");
Optional<User> optionalUser = userDao.findOne(Example.of(user));
}
在上面的示例中,给Example绑定了一个User对象,这个User对象中,设置了uid和username两个属性,意思是查询uid和username相匹配的数据,查询的条件是uid=? and username = ? ,同时满足给出的两个条件;
第二种是getOne方法
getOne(ID id):
使用这个方法,有延迟加载的功能ID是类上声明的泛型,实体类中主键的数据类型是什么,这个ID就是什么。
如果return null,要执行请求的时候,是不会进行数据库的查询的,这就是延迟加载,也叫做懒加载模式,当真正使用到数据的时候,才会去进行数据的查询,比如return user时,把查询到的数据返回给页面,这就使用到了查询的结果,因此才会真正的进行数据的查询操作。
在使用getOne()的时候需要注意一点,如果要操作中出现了这样的异常:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor
and no properties discovered to create BeanSerializer (to avoid exception, disable
SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.nnxy.entity.User$HibernateProxy$JmI5SdQM["hibernateLazyInitializer"])
出现这个异常,是因为SpringBoot中使用Hibernate的延迟加载功能,Hibernate每次都自动给操作的实体类添加一个hibernateLazyInitializer的临时属性。
当我们从查询中去获取加载的数据转换成JSON数据并返回时,在User实体类中找不到hibernateLazyInitializer这个临时属性,所以就报异常了。
如果返回JSON数据,在SpringMVC中默认使用的JSON转换工具,是jackson,因此报的是jackson相关包下的异常类InvalidDefinitionException,无效的定义异常。
也就是说,实际上,出现错误是在数据转换的这个环节,查询到的数据中多了一个属性,而实体类中并没有这个属性,所以,转换失败。
解决的方式也很简单,既然查询的数据中多了一个属性,那么在转换成JSON数据的时候,不使用这个属性就可以了,而jackson工具,提供了一个注解,在数据转换的时候,可以忽略掉指定的属性:
只要把指定的属性去掉,转换JSON的时候不使用它就可以了,@JsonIgnoreProperties就是用来干这个事情的,可以同时忽略多个属性,因为它的value是一个数组。
而@JsonIgnoreProperties注解,可以贴在类上,指定哪些属性不使用,也可以直接贴在属性上,这样就表示当前的属性被忽略,不参与JSON的转换。
在User类中,并没有hibernateLazyInitializer这个属性,所以就只能在类上贴,并指定把它忽略掉。
至此,查询单条数据的3种方式,都已经给大家作了说明,请大家好好测试!!
统计数据
 要知道数据表中有多少条数据,就需要进行统计查询,而要统计查询,也非常的简单,看代码:
至此,Spring Data JPA之CRUD的基本功能测试以及完结,下一章我们来真正的了解Spring Data JPA的高级用法。