jpa中::::
这是Project Student的一部分。 其他职位包括带有Jersey的Webservice Client,带有Jersey的 Webservice Server , 业务层 , 具有Spring Data的持久性 ,分片集成测试数据和Webservice Integration 。
我们已经介绍了CRUD的基本操作,但是并没有花太多时间。 Spring Data使包含基本搜索变得容易,但是拥有其他标准选项也很重要。 JPA标准查询是最重要的查询之一。
Spring Data JPA教程– JPA Criteria Queries [http://www.petrikainulainen.net]是对该材料的很好介绍。
设计决策
JPA标准 –我使用的是JPA标准搜索,而不是querydsl。 稍后我将返回querydsl。
局限性
违反封装 –此设计需要打破使每个层完全不了解其他层的实现细节的体系结构目标。 这是一个非常小的违反行为-仅JPA规范-我们仍然必须处理分页。 将它们放在一起,我觉得此时过分担心这个还为时过早。
Web服务 -不更新web服务客户端和服务器。 同样,我们仍然必须处理分页,并且无论如何我们现在要做的任何事情都必须更改。
重构先前的工作
我对以前的工作有三点改变。
findCoursesByTestRun()
我已经定义了方法:
List findCoursesByTestRun(TestRun testRun);
在课程库中。 那没有预期的效果。 我需要的是
List findCoursesByTestRun_Uuid(String uuid);
对调用代码进行适当的更改。 或者,您可以只使用下面讨论的JPA条件查询。
FinderService和ManagerService
这来自Jumpstart网站上的研究。 作者将标准服务接口分为两部分:
- FinderService –只读操作(搜索)
- ManagerService –读写操作(创建,更新,删除)
这很有道理,例如,当我们可以在类与方法级别上进行操作时,通过AOP添加行为会容易得多。 我在现有代码中做了适当的更改。
查找错误
我已经修复了FindBugs发现的许多问题。
元数据
我们首先允许对持久对象的元数据访问。 这使我们能够创建可以通过JPA实现进行优化的查询。
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@StaticMetamodel(Course.class)
public class Course_ {
public static volatile SingularAttribute<Course, TestRun> testRun;
}
和
@StaticMetamodel(TestRun.class)
public class TestRun_ {
public static volatile SingularAttribute<TestRun, String> uuid;
}
由于约定优于配置,因此需要类的名称。
- 有关此功能的讨论,请参见静态元数据 [jboss.org]。
技术指标
现在,我们可以使用现在可用的元数据创建查询规范。 这是一个稍微复杂的查询,因为我们需要深入研究结构。 (这不需要实际的联接,因为testrun uuid用作外键。)
public class CourseSpecifications {
/**
* Creates a specification used to find courses with the specified testUuid.
*
* @param testRun
* @return
*/
public static Specification<Course> testRunIs(final TestRun testRun) {
return new Specification<Course>() {
@Override
public Predicate toPredicate(Root<Course> courseRoot, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate p = null;
if (testRun == null || testRun.getUuid() == null) {
p = cb.isNull(courseRoot.<Course_> get("testRun"));
} else {
p = cb.equal(courseRoot.<Course_> get("testRun").<TestRun_> get("uuid"), testRun.getUuid());
}
return p;
}
};
}
}
一些文档建议我可以使用get(Course_.testRun)代替get(“ testRun”),但是eclipse在get()方法上将其标记为类型冲突。 你的旅费可能会改变。
Spring数据仓库
我们必须告诉Spring Data我们正在使用JPA Criteria查询。 这是通过扩展JpaSpecificationExecutor接口来完成的。
@Repository
public interface CourseRepository extends JpaRepository<Course, Integer>,
JpaSpecificationExecutor<Course> {
Course findCourseByUuid(String uuid);
List findCoursesByTestRunUuid(String uuid);
}
FinderService实施
现在,我们可以在我们的服务实现中使用JPA规范。 如上所述,使用JPA Criteria Specification违反了封装。
import static com.invariantproperties.sandbox.student.specification.CourseSpecifications.testRunIs;
@Service
public class CourseFinderServiceImpl implements CourseFinderService {
@Resource
private CourseRepository courseRepository;
/**
* @see com.invariantproperties.sandbox.student.business.FinderService#
* count()
*/
@Transactional(readOnly = true)
@Override
public long count() {
return countByTestRun(null);
}
/**
* @see com.invariantproperties.sandbox.student.business.FinderService#
* countByTestRun(com.invariantproperties.sandbox.student.domain.TestRun)
*/
@Transactional(readOnly = true)
@Override
public long countByTestRun(TestRun testRun) {
long count = 0;
try {
count = courseRepository.count(testRunIs(testRun));
} catch (DataAccessException e) {
if (!(e instanceof UnitTestException)) {
log.info("internal error retrieving classroom count by " + testRun, e);
}
throw new PersistenceException("unable to count classrooms by " + testRun, e, 0);
}
return count;
}
/**
* @see com.invariantproperties.sandbox.student.business.CourseFinderService#
* findAllCourses()
*/
@Transactional(readOnly = true)
@Override
public List<Course> findAllCourses() {
return findCoursesByTestRun(null);
}
/**
* @see com.invariantproperties.sandbox.student.business.CourseFinderService#
* findCoursesByTestRun(java.lang.String)
*/
@Transactional(readOnly = true)
@Override
public List<Course> findCoursesByTestRun(TestRun testRun) {
List<Course> courses = null;
try {
courses = courseRepository.findAll(testRunIs(testRun));
} catch (DataAccessException e) {
if (!(e instanceof UnitTestException)) {
log.info("error loading list of courses: " + e.getMessage(), e);
}
throw new PersistenceException("unable to get list of courses.", e);
}
return courses;
}
....
}
单元测试
我们的单元测试需要稍做更改才能使用规范。
public class CourseFinderServiceImplTest {
private final Class<Specification<Course>> sClass = null;
@Test
public void testCount() {
final long expected = 3;
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.count(any(sClass))).thenReturn(expected);
final CourseFinderService service = new CourseFinderServiceImpl(repository);
final long actual = service.count();
assertEquals(expected, actual);
}
@Test
public void testCountByTestRun() {
final long expected = 3;
final TestRun testRun = new TestRun();
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.count(any(sClass))).thenReturn(expected);
final CourseFinderService service = new CourseFinderServiceImpl(repository);
final long actual = service.countByTestRun(testRun);
assertEquals(expected, actual);
}
@Test(expected = PersistenceException.class)
public void testCountError() {
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.count(any(sClass))).thenThrow(new UnitTestException());
final CourseFinderService service = new CourseFinderServiceImpl(repository);
service.count();
}
@Test
public void testFindAllCourses() {
final List<Course> expected = Collections.emptyList();
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.findAll(any(sClass))).thenReturn(expected);
final CourseFinderService service = new CourseFinderServiceImpl(repository);
final List<Course> actual = service.findAllCourses();
assertEquals(expected, actual);
}
@Test(expected = PersistenceException.class)
public void testFindAllCoursesError() {
final CourseRepository repository = Mockito.mock(CourseRepository.class);
final Class<Specification<Course>> sClass = null;
when(repository.findAll(any(sClass))).thenThrow(new UnitTestException());
final CourseFinderService service = new CourseFinderServiceImpl(repository);
service.findAllCourses();
}
@Test
public void testFindCourseByTestUuid() {
final TestRun testRun = new TestRun();
final Course course = new Course();
final List<Course> expected = Collections.singletonList(course);
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.findAll(any(sClass))).thenReturn(expected);
final CourseFinderService service = new CourseFinderServiceImpl(repository);
final List actual = service.findCoursesByTestRun(testRun);
assertEquals(expected, actual);
}
@Test(expected = PersistenceException.class)
public void testFindCourseByTestUuidError() {
final TestRun testRun = new TestRun();
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.findAll(any(sClass))).thenThrow(new UnitTestException());
final CourseFinderService service = new CourseFinderServiceImpl(repository);
service.findCoursesByTestRun(testRun);
}
@Test
public void testFindCoursesByTestUuid() {
final TestRun testRun = new TestRun();
final Course course = new Course();
final List<Course> expected = Collections.singletonList(course);
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.findAll(any(sClass))).thenReturn(expected);
final CourseFinderService service = new CourseFinderServiceImpl(repository);
final List<Course> actual = service.findCoursesByTestRun(testRun);
assertEquals(expected, actual);
}
@Test(expected = PersistenceException.class)
public void testFindCoursesByTestUuidError() {
final TestRun testRun = new TestRun();
final CourseRepository repository = Mockito.mock(CourseRepository.class);
when(repository.findAll(any(sClass))).thenThrow(new UnitTestException());
final CourseFinderService service = new CourseFinderServiceImpl(repository);
service.findCoursesByTestRun(testRun);
}
....
}
通过使用@Begin方法,我可以消除很多重复的代码,但是我决定反对它来支持并行测试。
整合测试
我们终于来进行集成测试。 我们知道我们做对了,因为唯一的一行可以测试附加功能-计算数据库中的课程数量。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { BusinessApplicationContext.class, TestBusinessApplicationContext.class,
TestPersistenceJpaConfig.class })
@Transactional
@TransactionConfiguration(defaultRollback = true)
public class CourseServiceIntegrationTest {
@Resource
private CourseFinderService fdao;
@Resource
private CourseManagerService mdao;
@Resource
private TestRunService testService;
@Test
public void testCourseLifecycle() throws Exception {
final TestRun testRun = testService.createTestRun();
final String name = "Calculus 101 : " + testRun.getUuid();
final Course expected = new Course();
expected.setName(name);
assertNull(expected.getId());
// create course
Course actual = mdao.createCourseForTesting(name, testRun);
expected.setId(actual.getId());
expected.setUuid(actual.getUuid());
expected.setCreationDate(actual.getCreationDate());
assertThat(expected, equalTo(actual));
assertNotNull(actual.getUuid());
assertNotNull(actual.getCreationDate());
// get course by id
actual = fdao.findCourseById(expected.getId());
assertThat(expected, equalTo(actual));
// get course by uuid
actual = fdao.findCourseByUuid(expected.getUuid());
assertThat(expected, equalTo(actual));
// get all courses
final List<Course> courses = fdao.findCoursesByTestRun(testRun);
assertTrue(courses.contains(actual));
// count courses
final long count = fdao.countByTestRun(testRun);
assertTrue(count > 0);
// update course
expected.setName("Calculus 102 : " + testRun.getUuid());
actual = mdao.updateCourse(actual, expected.getName());
assertThat(expected, equalTo(actual));
// delete Course
mdao.deleteCourse(expected.getUuid(), 0);
try {
fdao.findCourseByUuid(expected.getUuid());
fail("exception expected");
} catch (ObjectNotFoundException e) {
// expected
}
testService.deleteTestRun(testRun.getUuid());
}
}
源代码
翻译自: https://www.javacodegeeks.com/2014/01/project-student-jpa-criteria-queries.html
jpa中::::