Quarkus PanacheRepository persist() not storing to MySQL

36 Views Asked by At

I'm new to hibernate-reactive, smallrye mutini, PanacheEntity, PanacheRepository.

At a high level I have:

  1. Tenant - a domain object (I don't want database related code in this)

  2. TenantDAO - A dao object that is specific to the db

  3. SqlRepository - A base class for repository that stores the dao and maps the dao <-> entity

  4. TenantRepository - extends SqlRepository and provides the TenantDAO object

  5. A rest handler for POST that calls the repository to store the tenant info.

Here's my code:

Tenant.java (Domain entity)

package com.my.platform.model.user;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class Tenant {
    private String id; // an application specific id (not the PanacheEntity id)
    private String name;
}

TenantDAO.java (object stored in the db):

package com.my.platform.repository.user;

import com.my.platform.model.user.Tenant;
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "Tenants")
public class TenantDAO extends PanacheEntity {
    private String entityId;
    private String name;

    public TenantDAO(Tenant tenant) {
        this.entityId = tenant.getId();
        this.shortName = tenant.getShortName();
    }

    public Tenant getEntity() {
        return new Tenant(entityId, name);
    }
}

I also have this file in the same older:

package-info.java

@io.quarkus.hibernate.orm.PersistenceUnit("<default>")
package com.my.platform.repository.user;

TenantRepository.java:

package com.my.platform.repository.user;

import com.my.platform.model.user.Tenant;
import com.mys.platform.repository.impl.sql.SqlRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.ext.Provider;

@ApplicationScoped
public class TenantRepository extends SqlRepository<Tenant, TenantDAO> {
    @Override
    public TenantDAO createDAO(Tenant tenant) {
        return new TenantDAO(tenant);
    }
}

SqlRepository:

package com.my.platform.repository.impl.sql;

import java.util.List;

import io.quarkus.hibernate.reactive.panache.PanacheRepository;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;

@ApplicationScoped
public abstract class SqlRepository<E, DAO> implements PanacheRepository<DAO> {
    @Override
    @WithSession
    public Uni<List<E>> list() {
        return listAll().onItem().transform(entityList -> 
            entityList.stream().map(entityDao -> entityDao.getEntity()).toList()
        );
    }

    @Override
    @WithSession
    public Uni<E> findByEntityId(String id) {
        return find("entityId", id).firstResult().map(dao -> (E)dao.getEntity());
    }

    @Override
    @Transactional
    public Uni<E> add(E entity) {
        DAO dao = createDAO(entity);
        persist(dao);
        return Uni.createFrom().item(dao.getEntity());
    }

    abstract protected DAO createDAO(E);
}

REST resource method (TenantCreationData is a simple POJO for getting tenant info):

    @POST
    @Transactional
    public Uni<Response> provision(TenantCreationData tenantCreationData) {
        Tenant tenant = new Tenant(tenantCreationData.getTenantName());
        return tenantRepository.add(tenant)
                .onItem()
                .transform(addedTenant -> (addedTenant != null) ? Response.ok(addedTenant) : Response.status(Response.Status.NOT_FOUND))
                .onItem()
                .transform(Response.ResponseBuilder::build);
    }

Note:
I don't fully understand the @WithSession as I couldn't find good documentation around that which explains exactly what this session is. I got the code to work by adding this annotation to the get() methods (otherwise it errors out with some session relayed error). And if I add the same @WithSession annotation to the add() method then I get an obscure error that I couldn't find on google search -

Error id 1aff6b15-313d-47a9-9422-97afdca21e95-1, java.lang.IllegalStateException: HR000068: This method should exclusively be invoked from a Vert.x EventLoop thread; currently running on thread 'executor-thread-1'

Anyway, the code as I have shown above doesn't give me any errors but when an HTTP post to it goes through all the code including persist() but it's not saved to the DB.

I am not sure if this is something to do with the session -- persist() doesn't throw any exceptions but data is not saved. Same with persistAndFlush(). I debugged into persist() and deep down inside the persist() method of AbstractJpaOperations, my breakpoint at if(!session.contains(entity)) doesn't hit (see below). Failing somewhere around session handling or "session chaining"? I'm not sure I fully understand the sessions here.

    public Uni<Void> persist(Uni<Mutiny.Session> sessionUni, Object entity) {
        return sessionUni.chain(session -> {
            if (!session.contains(entity)) {
                return session.persist(entity);
            }
            return Uni.createFrom().nullItem();
        });
    }

application.properties:

# DataSource configuration
quarkus.datasource.url=<mysql db>
quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
quarkus.datasource.username=<username>
quarkus.datasource.password=<password>

# Default persistence unit
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.datasource=<default>
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.multitenant=SCHEMA

# Tenant persistence unit
quarkus.hibernate-orm."tenant".database.generation=none
quarkus.hibernate-orm."tenant".datasource=<default>
quarkus.hibernate-orm."tenant".log.sql=true
quarkus.hibernate-orm."tenant".multitenant=SCHEMA

# Hibernate ORM configuration
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.database.default-schema=shared

Multi-tenancy config classes:

@PersistenceUnitExtension
@RequestScoped
public class DefaultTenantResolver implements TenantResolver {
    private static final String DEFAULT_TENANT = "shared";

    @Override
    public String getDefaultTenantId() {
        return DEFAULT_TENANT;
    }

    @Override
    public String resolveTenantId() {
        return DEFAULT_TENANT;
    }
}
@PersistenceUnitExtension("tenant")
@RequestScoped
public class CustomTenantResolver implements TenantResolver {
    private static final String DEFAULT_TENANT = "shared";

    @Inject
    RoutingContext context;

    @Inject
    UserService userService;

    @Override
    public String getDefaultTenantId() {
        return DEFAULT_TENANT;
    }

    @Override
    public String resolveTenantId() {
        String tenantName = context.request().getHeader("tenant");
        return tenantName;
    }
}

Any thoughts on what I am missing?

When I POST to my resource, I expect the data to be saved. But it's not saved.

1

There are 1 best solutions below

0
On

RESOLVED!

Changed this: @Override @Transactional public Uni add(E entity) { DAO dao = createDAO(entity); persist(dao); return Uni.createFrom().item(dao.getEntity()); }

to:

@Override
public Uni<E> add(E entity) {
    DAO dao = createDAO(entity);
    return persist(dao).onItem().transform(addedDao -> (addedDao != null) ? dao.getEntity() : null);
}