正确构建 Jersey WS + Hibernate 以实现并发

Correctly architecting Jersey WS + Hibernate to concurrency

我使用 Jersey 2 和 Hibernate 5 开发了一个 Web 服务。我将解释 Hibernate 的架构。

我使用 Dao 模式:只有一个独特的 DAO,尽管我有几个 类。我还没有创建 Dao 应该实现的任何接口。我认为以前的建筑在建筑方面根本不对。这是我的道:

public class PtDao{

    /*
     * Atributos
     */
    private static PtDao INSTANCE = null;

    private Session currentSession;
    private Transaction currentTransaction;
    private SessionFactory factory;

    /*
     * Métodos
     */
    private PtDao() throws HibernateException, ConfigurationException{
        factory = getSessionFactory();
    }

    public static PtDao getInstance() throws HibernateException, ConfigurationException{
        if(INSTANCE == null){
            INSTANCE = new PtDao();
        }

        return INSTANCE;
    }

    public Session openCurrentSession() throws HibernateException {
        currentSession = factory.openSession();
        return currentSession;
    }

    public void closeCurrentSession() {
        currentSession.close();
        currentSession = null;
    }

    private SessionFactory getSessionFactory() throws HibernateException, ConfigurationException{

        if(getFactory() == null){
            Configuration cfg = new Configuration();

            cfg.setProperty("hibernate.dialect", "org.hibernate.spatial.dialect.postgis.PostgisDialect");
            cfg.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver");

            PropertiesConfiguration config = PropertiesConfiguration.getInstance();

            String strUrl = "jdbc:postgresql://" + config.getDb_host() + ":" + config.getDb_port() + "/" + config.getDb_database();

            cfg.setProperty("hibernate.connection.url", strUrl);
            cfg.setProperty("hibernate.connection.username", config.getDb_user());
            cfg.setProperty("hibernate.connection.password", config.getDb_passwd());

            cfg.setProperty("hibernate.hbm2ddl.auto", "update");
            cfg.setProperty("hibernate.show_sql", "false");
            cfg.setProperty("hibernate.format_sql", "false");
            cfg.setProperty("hibernate.generate_statistics", "false");

            // C3p0 connection pool
            cfg.setProperty("connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider");
            cfg.setProperty("c3p0.min_size", "7");
            cfg.setProperty("c3p0.max_size", "10000");
            cfg.setProperty("c3p0.timeout", "1000");
            cfg.setProperty("c3p0.idle_test_period", "2000");
            cfg.setProperty("c3p0.preferredTestQuery", "select 1;");

            cfg.addAnnotatedClass(Tarifa.class);
            cfg.addAnnotatedClass(Provincia.class);
            cfg.addAnnotatedClass(Comarca.class);
            cfg.addAnnotatedClass(Municipio.class);
            cfg.addAnnotatedClass(Salto.class);

            StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
                    .applySettings(cfg.getProperties());
            setFactory(cfg.buildSessionFactory(builder.build()));

        }

        return getFactory();

    }

    public Session getCurrentSession() {
        return currentSession;
    }

    public void setCurrentSession(Session currentSession) {
        this.currentSession = currentSession;
    }

    public Transaction getCurrentTransaction() {
        return currentTransaction;
    }

    public void setCurrentTransaction(Transaction currentTransaction) {
        this.currentTransaction = currentTransaction;
    }

    public Transaction beginTransaction(){
        this.currentTransaction = currentSession.beginTransaction();
        return this.getCurrentTransaction();
    }

    public void commitTransaction(){
        if(currentTransaction != null)
            currentTransaction.commit();
    }

    public void rollbackTransaction(){
        if(currentTransaction != null)
            currentTransaction.rollback();
    }

    public SessionFactory getFactory() {
        return factory;
    }

    public void setFactory(SessionFactory factory) {
        this.factory = factory;
    }

    public void finish() throws HibernateException {
        Session session = getCurrentSession();
        if(session != null)
            session.close();
        if(factory != null)
            factory.close();
    }


    @SuppressWarnings("unchecked")
    public List<Municipio> getMunicipios (){
        Query q = currentSession.createQuery("from Municipio");
        return q.list();
    }

    public Municipio getMunicipioByCoor(Point coor) throws NoExisteMunicipioException{
        Criteria crit = currentSession.createCriteria(Municipio.class);
        crit.add(SpatialRestrictions.contains("geom", coor));
        @SuppressWarnings("unchecked")
        List<Municipio> l = crit.list();
        if (l.isEmpty())
            throw new NoExisteMunicipioException(coor);
        return l.get(0);
    }

    public Comarca getComarcaById(Integer pIdComarca) throws NoExisteComarcaException{
        Criteria crit = currentSession.createCriteria(Comarca.class);
        crit.add(Restrictions.eq("id", pIdComarca));
        crit.setMaxResults(1);
        @SuppressWarnings("rawtypes")
        List comarcas = crit.list();
        if(comarcas.isEmpty())
            throw new NoExisteComarcaException(pIdComarca);
        return (Comarca) comarcas.get(0);
    }

    public Boolean existenSaltosComarcas(){
        Criteria crit = currentSession.createCriteria(Salto.class);
        @SuppressWarnings("rawtypes")
        List saltos = crit.list();
        Boolean tiene = false;
        if(saltos != null){
            if(!saltos.isEmpty()){
                tiene = true;
            }
        }
        return tiene;
    }

    public void crearSalto(Comarca pComarcaOrigen, Comarca pComarcaDestino, int pNumeroSaltos) {
        Salto elSalto = new Salto(pComarcaOrigen, pComarcaDestino, pNumeroSaltos);
        currentSession.save(elSalto);
    }

    public Integer getCantidadSaltos(Municipio municipioInicio, Municipio municipioFin) throws SinViajesEntreZonasException {
        System.out.println("getCantidadSaltos()");
        Integer saltos = null;
        Criteria crit = currentSession.createCriteria(Salto.class);
        crit.add(
            Restrictions.and(
                Restrictions.eq("origen", municipioInicio.getZona_mugi()), 
                Restrictions.eq("destino", municipioFin.getZona_mugi())
            )
        );
        crit.setMaxResults(1);
        @SuppressWarnings("rawtypes")
        List res = crit.list();
        if(res != null){
            if(!res.isEmpty()){
                saltos = ((Salto) res.get(0)).getNumeroSaltos();
            }else{
                throw new SinViajesEntreZonasException(municipioInicio.getZona_mugi(), municipioFin.getZona_mugi());
            }
        }else{
            throw new SinViajesEntreZonasException(municipioInicio.getZona_mugi(), municipioFin.getZona_mugi());
        }
        return saltos;
    }

    public Boolean existenTarifas() {
        Boolean existen = false;

        Criteria crit = currentSession.createCriteria(Tarifa.class);
        crit.setProjection(Projections.rowCount());
        Long cant = (Long) crit.uniqueResult();
        if(cant > 0){
            existen = true;
        }

        return existen;
    }

    public void crearTarifa(Tarifa pTarifa) {
        currentSession.saveOrUpdate(pTarifa);
    }

    public Tarifa getTarifa(TipoTarifa pTipoTarifa) throws NoExisteTarifaException {
        Criteria crit = currentSession.createCriteria(Tarifa.class);
        crit.add(Restrictions.idEq(pTipoTarifa));

        Tarifa t = (Tarifa) crit.uniqueResult();

        if(t == null){
            throw new NoExisteTarifaException(pTipoTarifa);
        }else{
            return t;
        }

    }

}

我有一个服务层,它执行在 Web 服务端点中启动的操作:

public class PtDaoService {

    private PtDao dao;

    public PtDaoService() throws HibernateException, ConfigurationException{
        dao = PtDao.getInstance();
    }

    public void finish() throws HibernateException {
        dao.finish();
    }

    public void imprimirZonas(){
        try {
            dao.openCurrentSession();
            List<Municipio> municipios = dao.getMunicipios();
            for(Municipio m:municipios){
                System.out.println(m.toString());
            }
            dao.closeCurrentSession();
        } catch (HibernateException e) {
            System.err.println("Error HibernateException: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public Municipio getMunicipioByCoor(Point coor) throws NoExisteMunicipioException{
        try {
            dao.openCurrentSession();
            Municipio municipio = dao.getMunicipioByCoor(coor);         
            dao.closeCurrentSession();
            return municipio;
        } catch (HibernateException e) {
            System.err.println("Error HibernateException: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }

    public void inicializarTarifasSiNoExisten(){
        boolean construyendo = false;
        try {
            dao.openCurrentSession();
            if(!dao.existenTarifas()){
                construyendo = true;
                dao.beginTransaction();
                Tarifa ocasional = new Tarifa(
                        TipoTarifa.Ocasional, 
                        "Sin condiciones", 
                        Money.of(CurrencyUnit.EUR, 1.70), 
                        Money.of(CurrencyUnit.EUR, 2.45), 
                        Money.of(CurrencyUnit.EUR, 4.65), 
                        Money.of(CurrencyUnit.EUR, 6.85), 
                        Money.of(CurrencyUnit.EUR, 8.90), 
                        Money.of(CurrencyUnit.EUR, 12.00));
                dao.crearTarifa(ocasional);

                Tarifa tramo1 = new Tarifa(
                        TipoTarifa.Tramo1, 
                        "Descuento del 45%", 
                        Money.of(CurrencyUnit.EUR, 0.93), 
                        Money.of(CurrencyUnit.EUR, 1.35), 
                        Money.of(CurrencyUnit.EUR, 2.56), 
                        Money.of(CurrencyUnit.EUR, 3.77), 
                        Money.of(CurrencyUnit.EUR, 4.90), 
                        Money.of(CurrencyUnit.EUR, 6.60));
                dao.crearTarifa(tramo1);
                dao.commitTransaction();
            }
            dao.closeCurrentSession();
        } catch (HibernateException e) {
            if(construyendo)
                dao.rollbackTransaction();
            System.err.println("Error HibernateException: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public void inicializarSaltosComarcasSiNoExisten(){
        //a method
    }

    public Integer getCantidadSaltos(Double pOriginLatitude, Double pOriginLongitude, Double pDestinationLatitude,
            Double pDestinationLongitude) throws SinViajesEntreZonasException, NoExisteMunicipioException, ErrorEnGetCantidadSaltosException {

        System.out.println("getCantidadSaltos() [ pOriginLatitude=" + pOriginLatitude + ", pOriginLongitude=" + pOriginLongitude + ", pDestinationLatitude=" + pDestinationLatitude + ", pDestinationLongitude=" + pDestinationLongitude + " ]");

        Point inicio = GeometriesFactory.createPoint(pOriginLatitude, pOriginLongitude);
        Point fin = GeometriesFactory.createPoint(pDestinationLatitude, pDestinationLongitude);

        try {
            dao.openCurrentSession();
            Municipio municipioInicio = dao.getMunicipioByCoor(inicio);         
            Municipio municipioFin = dao.getMunicipioByCoor(fin);

            Integer saltos = dao.getCantidadSaltos(municipioInicio, municipioFin);

            dao.closeCurrentSession();

            return saltos;
        } catch (HibernateException e) {
            System.err.println("Error HibernateException: " + e.getMessage());
            e.printStackTrace();
            throw new ErrorEnGetCantidadSaltosException();
        }

    }

    public Money getTarifaDeSaltos(Boolean pTieneTarjeta, Integer pSaltos) throws NoExisteTarifaException, ErrorEnGetTarifaException {
        TipoTarifa tipoTarifa;
        if(pTieneTarjeta){
            tipoTarifa = TipoTarifa.Tramo1;
        }else{
            tipoTarifa = TipoTarifa.Ocasional;
        }
        Tarifa laTarifa = null;
        try {
            dao.openCurrentSession();
            laTarifa = dao.getTarifa(tipoTarifa);
            dao.closeCurrentSession();
            return laTarifa.getTarifaParaLosSaltos(pSaltos);
        } catch (HibernateException e) {
            System.err.println("Error HibernateException: " + e.getMessage());
            e.printStackTrace();
            throw new ErrorEnGetTarifaException(tipoTarifa);
        }

    }

}

这是我端点的一段代码:

@Path("FareManager")
public class TarifasService {

private PtDaoService dao;
    ...
    @ValidateOnExecution
    @GET
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("{operator}/fare")
    public Response getTarifa(
        @NotNull @PathParam("operator") String pOperator,
        @NotNull @QueryParam("start_lat") Double pOriginLatitude,
        @NotNull @QueryParam("start_lon") Double pOriginLongitude,
        @NotNull @QueryParam("end_lat") Double pDestinationLatitude,
        @NotNull @QueryParam("end_lon") Double pDestinationLongitude,
        @QueryParam("user_id") String pUserIdentification,
        @QueryParam("provider") String pTransportProvider,
        @QueryParam("card") Boolean pCard
    ){

        String json = "";
        try {

            if(pOperator==null || pOriginLatitude==null || pOriginLongitude==null || pDestinationLatitude==null || pDestinationLongitude==null || pCard==null || pTransportProvider==null){
            String[] params = {"operator", "start_lat", "start_lon", "end_lat", "end_lon", "card", "provider"};
                throw new ParametrosInsuficientesException(params);
            }

            Integer saltos = dao.getCantidadSaltos(pOriginLatitude, pOriginLongitude, pDestinationLatitude, pDestinationLongitude);

            Money tarifa = dao.getTarifaDeSaltos(pCard, saltos);
            BigDecimal precio = tarifa.getAmount();
            Currency moneda = tarifa.getCurrencyUnit().toCurrency();

            FareResponse response = new FareResponse(pTransportProvider, precio, moneda);
            json = mWriter.writeValueAsString(response);
            logHelper.logGetTarifa(pOriginLatitude, pOriginLongitude, pDestinationLatitude, pDestinationLongitude, precio, moneda, saltos, pCard, pOperator, pUserIdentification, pTransportProvider);
            return Response.ok(json, MediaType.APPLICATION_JSON).build();
        } catch (Exception e) {
            System.err.println("Error: " + e.getLocalizedMessage());
            e.printStackTrace();
            ExceptionResponse errResp = new ExceptionResponse(e);
            try {
                json = mWriter.writeValueAsString(errResp);
                return Response.serverError().tag("Error").entity(json).build();
            } catch (JsonProcessingException e1) {
                System.err.println("Error: " + e.getLocalizedMessage());
                e.printStackTrace();
                return Response.serverError().tag("Error").build();
            }
        }
    }

问题是 Web 服务应该允许同时对同一方法发出多个请求。如果我一次多次执行一个方法(在浏览器中,例如按几次 f5),我会得到异常痕迹,而且似乎是随机的。

你能看出我的代码有什么问题吗?

提前致谢

编辑 这是例外情况:

14:05:00.135 [http-apr-9090-exec-6] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - This statement has been closed.
    Error HibernateException: could not extract ResultSet
    org.hibernate.exception.GenericJDBCException: could not extract ResultSet
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
        at org.hibernate.loader.Loader.getResultSet(Loader.java:2115)
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1898)
        at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1874)
        at org.hibernate.loader.Loader.doQuery(Loader.java:919)
        at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:336)
        at org.hibernate.loader.Loader.doList(Loader.java:2610)
        at org.hibernate.loader.Loader.doList(Loader.java:2593)
        at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2422)
        at org.hibernate.loader.Loader.list(Loader.java:2417)
        at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:109)
        at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1787)
        at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:363)
        at com.ingartek.dao.PtDao.getMunicipioByCoor(PtDao.java:192)
        at com.ingartek.dao.PtDaoService.getCantidadSaltos(PtDaoService.java:326)
        at com.ingartek.ws.tarifas.TarifasService.getTarifa(TarifasService.java:195)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.invoke(ResourceMethodInvocationHandlerFactory.java:81)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.run(AbstractJavaResourceMethodDispatcher.java:144)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
        at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:160)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
        at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:326)
        at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
        at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
        at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
        at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
        at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
        at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
        at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2500)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2489)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Unknown Source)
    Caused by: org.postgresql.util.PSQLException: This statement has been closed.
        at org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:893)
        at org.postgresql.jdbc.PgStatement.getMaxRows(PgStatement.java:479)
        at org.postgresql.jdbc.PgStatement.createResultSet(PgStatement.java:181)
        at org.postgresql.jdbc.PgStatement$StatementResultHandler.handleResultRows(PgStatement.java:231)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1947)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:200)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:424)
        at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:161)
        at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:114)
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70)
        ... 65 more
    Error: Error a la hora de consultar la cantidad de saltos.
    com.ingartek.exception.ErrorEnGetCantidadSaltosException: Error a la hora de consultar la cantidad de saltos.
        at com.ingartek.dao.PtDaoService.getCantidadSaltos(PtDaoService.java:336)
        at com.ingartek.ws.tarifas.TarifasService.getTarifa(TarifasService.java:195)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.invoke(ResourceMethodInvocationHandlerFactory.java:81)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.run(AbstractJavaResourceMethodDispatcher.java:144)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
        at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:160)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
        at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:326)
        at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
        at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
        at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
        at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
        at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
        at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
        at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2500)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2489)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Unknown Source)

我看到 3 个问题:

  1. 你的交易管理有误
  2. 您的 dao 不是线程安全的(这是触发您看到的堆栈跟踪的这一点)。
  3. 您在服务级别隐藏异常

关于 1,您应该在 finally 块中提交 tx / 关闭休眠会话。

关于2,你不能使用单例的字段来存储当前的交易和当前的会话。不完全正确,但有状态对象应该包装在无状态对象的 ThreadLocal 字段中。这管理起来相对复杂,您应该为此使用库(例如 spring-framework tx services)。

关于3,如果服务隐藏了异常,你永远不知道在控制器中是否发生了错误,以及是否应该继续处理...