ID为""的对象当使用GAE的JPA中的相同方法调用多个JpaRepository时,由不同的对象管理器管理

时间:2017-10-14 05:22:20

标签: java google-app-engine jpa spring-data-jpa datanucleus

我正在使用Google Apps Engine(GAE)和JPA的JpaRepository接口以及GAE DataStore的Data Nucleus JPA实现:

package com.appspot.repo;

import com.appspot.model.BusStop;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by eljah32 on 10/8/2017.
 */
public interface BusStopRepository extends JpaRepository<BusStop, String> {

}

和另一个回购

package com.appspot.repo;

import com.appspot.model.BusNode;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
 * Created by eljah32 on 10/8/2017.
 */
public interface BusNodeRepository extends JpaRepository<BusNode, String> {
    List<BusNode> findTop1ByLatitude(double latitude);
    List<BusNode> findTop1ByLongitude(double longitude);
}

然后是模型实体:

package com.appspot.model;

import org.datanucleus.api.jpa.annotations.Extension;

import javax.persistence.*;

/**
 * Created by eljah32 on 10/8/2017.
 */

@Entity
public class BusStop {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
    public String key;

    @OneToOne
    public BusNode busNode;

    public String name;
    public String name_ru;
    public String name_tt;
    public String name_en;
}

和其他模型

package com.appspot.model;

import org.datanucleus.api.jpa.annotations.Extension;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Created by eljah32 on 10/8/2017.
 */

@Entity
public class BusNode {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
    private String id;
    @org.datanucleus.api.jpa.annotations.Index(unique = "false", name="LAT")
    public double latitude;
    @org.datanucleus.api.jpa.annotations.Index(unique = "false", name="LON")
    public double longitude;

}

然后我在控制器中使用相同的方法调用jpa repo方法(参见getBusStops()):

package com.appspot.controller;

import com.appspot.model.BusNode;
import com.appspot.model.BusRoute;
import com.appspot.model.BusStop;
import com.appspot.repo.BusNodeRepository;
import com.appspot.repo.BusRouteRepository;
import com.appspot.repo.BusStopRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.persistence.EntityManager;
import java.util.List;

/**
 * Created by eljah32 on 10/8/2017.
 */

@Controller
@RequestMapping("/bus")
@Transactional
public class BatchStorageController {
    private static Logger logger = LoggerFactory.getLogger(BatchStorageController.class);

    @Autowired
    BusNodeRepository busNodeRepository;

    @Autowired
    BusStopRepository busStopRepository;

    @Autowired
    BusRouteRepository busRouteRepository;


    @RequestMapping(value = "/nodes", method = RequestMethod.GET)
    public @ResponseBody
    List<BusNode> getBusRoutes() {
        BusNode busNode=new BusNode();
        busNode.latitude=50.4;
        busNode.longitude=45.5;
        BusNode busNode2=new BusNode();
        busNode2.latitude=50.5;
        busNode2.longitude=45.3;
        busNodeRepository.save(busNode);
        busNodeRepository.save(busNode2);
        return busNodeRepository.findAll();
    }

    @RequestMapping(value = "/nodes/{latitude}", method = RequestMethod.GET)
    public @ResponseBody
    List<BusNode> getByLatitude(@PathVariable("latitude") double latitude) {
        List<BusNode> busNode=busNodeRepository.findTop1ByLatitude(latitude);
        return busNode;
    }

    @Transactional()
    @RequestMapping(value = "/stops", method = RequestMethod.GET)
    public @ResponseBody
    List<BusStop> getBusStops() {
        BusNode busNode=busNodeRepository.findTop1ByLatitude(50.5).get(0);
        //BusNode busNode3=new BusNode();
        //busNode3.latitude=50.2;
        //busNode3.longitude=45.2;
        //busNodeRepository.save(busNode3);

        BusStop busStop=new BusStop();
        busStop.busNode=busNode;
        busStop.name="Idel";
        busStop.name_en="Idel";
        busStop.name_ru="Idel";
        busStop.name_tt="Idel";

        busStopRepository.save(busStop); // here the exception occurs
        return busStopRepository.findAll();
    }
}

因此,在调用该方法之后,我收到的消息是带有id&#34; aglidXNyb3V0ZXNyFAsSB0J1c05vZGUYgICAgICAoAgM&#34;由不同的对象管理器管理;嵌套异常是javax.persistence.PersistenceException:id为#&#34; aglidXNyb3V0ZXNyFAsSB0J1c05vZGUYgICAgICAoAgM&#34;由busStopRepository.save(busStop)上的不同对象管理器管理;

那么如何在同一方法中将第一个存储库中的一个实体的提取与第二个存储库中另一个实体的存储相结合呢?我在其他JPA实现中从未遇到过同样的问题。

UPD:

pom.xml中使用的库:

  <appengine.version>1.9.30</appengine.version>
  ...
  <!-- Spring data jpa -->
    <dependency>
        <groupId>com.google.appengine</groupId>
        <artifactId>appengine-api-1.0-sdk</artifactId>
        <version>${appengine.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.3.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>3.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>3.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.4.Final</version>
    </dependency>

    <dependency>
        <groupId>org.datanucleus</groupId>
        <artifactId>datanucleus-enhancer</artifactId>
        <version>3.1.1</version>
    </dependency>
    <dependency>
        <groupId>com.google.appengine.orm</groupId>
        <artifactId>datanucleus-appengine</artifactId>
        <version>2.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.datanucleus</groupId>
        <artifactId>datanucleus-core</artifactId>
        <version>3.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.datanucleus</groupId>
        <artifactId>datanucleus-api-jpa</artifactId>
        <version>3.1.3</version>
    </dependency>

UPD 2。

具有当前问题的项目可以在https://github.com/Eljah/busroutes-gae/tree/0.1(已标记)

上进行

1 个答案:

答案 0 :(得分:0)

尽管我没有得到Data Nucleus和Google的JPA实现的内部结构,但为什么我得到了例外,我在上面说过,我已经解决了我面临的问题。如果您遇到相同的异常,可能是您正在尝试解决读取许多实体并在同一事务中更新少数的问题(因为由于上述异常,事务更新不会失败)。

问题在架构上得到解决:1)您应该在服务层中创建单独的方法来读取实体或获取计数而不使它们成为事务2)在服务层中创建单独的方法,将非事务方法的结果作为参数获取并更新实体;使这些方法在控制器层或控制器下面的自定义层中进行事务处理,但在服务上方分别调用这些方法!

我的错是我认为Spring的@Transactional注释有意义,即使使用@Transactionl从另一个方法调用@Transactional的方法也是如此。这是错误的:由于注释的Aspect性质,只有从外部类对象调用方法时才有意义。因此,在我的示例中,我将整个调用放在唯一的事务中(具有太多实体异常)或者在非事务内(因此获取具有id的Object ...由不同的对象管理器管理)。因此,将非事务行为和事务行为分离到不同的方法并从外部调用它们对我有帮助。