项目学生: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;
}

由于约定优于配置,因此需要类的名称。

技术指标

现在,我们可以使用现在可用的元数据创建查询规范。 这是一个稍微复杂的查询,因为我们需要深入研究结构。 (由于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标准规范违反了封装。

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());
    }
}

源代码

参考: 项目学生: Invariant Properties博客上来自JCG合作伙伴 Bear Giles的JPA标准查询

翻译自: https://www.javacodegeeks.com/2014/01/project-student-jpa-criteria-queries.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值