首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >REST + Spring数据JPA实体图+实体未完全加载+缓存

REST + Spring数据JPA实体图+实体未完全加载+缓存
EN

Stack Overflow用户
提问于 2022-09-14 14:11:02
回答 1查看 90关注 0票数 0

当在存储库查询中加载实体时,不完全使用规范和实体图,则缓存数据。但是只有当方法在REST端点上被调用时。当该方法被调用调度时,一切都很好,数据没有被缓存。

数据模型:

代码语言:javascript
复制
School
  |_ id
  |_ name
  |_ SchoolClass[]
           |______ id
           |______ name
代码语言:javascript
复制
@Entity
@NamedEntityGraphs(
    value = [
        NamedEntityGraph(
            name = "School.classes",
            attributeNodes = [
                NamedAttributeNode(
                    "classes"
                )
            ]
        )
    ]
)
class School {

    constructor(name: String) {
        this.name = name
    }

    @Id
    @GeneratedValue
    var id: Long = 0

    @Column(length = 30, nullable = false)
    var name: String = ""

    @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = false, mappedBy = "school")
    var classes: MutableSet<SchoolClass> = mutableSetOf()

    override fun toString(): String {
        return "School(id=$id, name='$name', classes=${classes.joinToString { it.name }})"
    }
}

interface SchoolRepository : JpaRepository<School, Long>, JpaSpecificationExecutor<School> {

    @EntityGraph("School.classes")
    override fun findAll(spec: Specification<School>?): List<School>

    @Query(
        """
        select s from School s where s.id in :ids
    """
    )
    @EntityGraph("School.classes")
    fun findAllByIdWithClasses(@Param("ids") ids: Set<Long>): List<School>
}

object SchoolSpecifications {

    fun name(name: String) =
        Specification<School> { root, _, criteriaBuilder ->
            criteriaBuilder.equal(root.get<String>("name"), name)
        }

    fun className(className: String) =
        Specification<School> { root, _, criteriaBuilder ->
            val classes = root.join<School, SchoolClass>("classes")

            criteriaBuilder.equal(classes.get<String>("name"), className)
        }
}
代码语言:javascript
复制
@Entity
class SchoolClass {

    constructor(name: String, school: School) {
        this.name = name
        this.school = school
    }

    @Id
    @GeneratedValue
    var id: Long = 0

    @Column(length = 30, nullable = false)
    var name: String = ""

    @ManyToOne(optional = false)
    var school: School? = null

    override fun toString(): String {
        return "SchoolClass(id=$id, name='$name', school=${school?.name})"
    }
}

interface SchoolClassRepository : JpaRepository<SchoolClass, Long>
代码语言:javascript
复制
@Service
class SchoolService
@Autowired
constructor(
    private val schoolRepository: SchoolRepository,
    private val schoolClassRepository: SchoolClassRepository
) {

    fun initData() {
        schoolClassRepository.deleteAll()
        schoolRepository.deleteAll()

        createSchool("Goethe-School", listOf("1A", "1B", "1C", "1D"))
        createSchool("Schiller-School", listOf("5A", "6A", "5B", "6B"))
    }

    private fun createSchool(schoolName: String, classes: List<String>) {
        val school = School(schoolName)

        val savedSchool = schoolRepository.saveAndFlush(school)

        classes.forEach {
            val newClass = SchoolClass(it, savedSchool)
            schoolClassRepository.saveAndFlush(newClass)
        }
    }

    fun allFine(): List<School> {
        return schoolRepository.findAll(SchoolSpecifications.name("Goethe-School"))
    }

    fun crazyStuff(): List<School> {
        val schoolIds = schoolRepository.findAll(SchoolSpecifications.className("1C"))
            .map { it.id }
            .toSet()

        return schoolRepository.findAllByIdWithClasses(schoolIds)
    }
}

有趣的部分是:

代码语言:javascript
复制
@RestController
class Controller {

    @Autowired
    lateinit var schoolService: SchoolService

    @GetMapping("/")
    fun query() {
        printSchool()
    }

    @Scheduled(fixedDelay = 5000)
    fun scheduled() {
        printSchool()
    }

    private fun printSchool() {
        val schools = schoolService.crazyStuff()
        println("=crazy stuff= $schools")

        val schools2 = schoolService.allFine()
        println("=all fine= $schools2")
    }
}

业务逻辑是:用类'1C‘加载所有学校,提取id,然后用这些id的更新加载所有学校。

在REST-Controller中,输出是:

=crazy stuff= [School(id=1, name='Goethe-School', classes=1C)] =all fine= [School(id=1, name='Goethe-School', classes=1C)]

在计划的任务中,输出与预期的相同:

=crazy stuff= [School(id=1, name='Goethe-School', classes=1D, 1B, 1C, 1A)] =all fine= [School(id=1, name='Goethe-School', classes=1A, 1C, 1B, 1D)]

这种行为的原因是什么?以及如何使用规范查询和实体图禁用未满载实体的缓存?

EN

回答 1

Stack Overflow用户

发布于 2022-09-14 14:43:52

这肯定是由于事务范围的限制。

很难确定,但是由于您没有事务去标记,所以我将假设您使用的是视图反模式中的开放会话

这使得您在REST调用中使用所有发生在单个事务中的事情。因此,第一个加载的学校把它放在第一级缓存,它停留在那里,并在随后的调用返回。

在预定的调用中,您根本没有任何显式事务,OSIV也没有效果,因为这是绑定到不存在的web请求上的。因此,为每个存储库调用创建和关闭新事务,每次重新创建和销毁第一级缓存。因此,您实际上根本没有任何缓存。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73718342

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档