Соединение закрыто при тестировании репозитория с использованием тестовых контейнеров с шаблоном JDBC

Я пытаюсь протестировать приведенный ниже класс UserRepositoryImpl, который вставляет User в базу данных MySql.

@Repository
public class UserRepositoryImpl implements UserRepository {

    private final JdbcTemplate template;

    public UserRepositoryImpl(DataSource dataSource) {
        this.template = new JdbcTemplate(dataSource);
    }

    @Override
    public Integer insert(User user) {
        String sql = "INSERT INTO EXPENSE_TRACKER.USERS (USERNAME, PASSWORD, FIRSTNAME, LASTNAME, EMAIL) VALUES (?, ?, ?, ?, ?)";
        return template.update(sql, user.getUsername(), user.getPassword(), user.getFirstName(), user.getLastName(), user.getEmail());
    }
}

Мне нужно протестировать этот метод, и по этой причине я создал следующий простой тест, использующий тестконтейнеры.

@SpringBootTest
class UserRepositoryImplSpec extends TestContainersSpec {

    @Subject
    UserRepositoryImpl repository

    def setup() {
        repository = new UserRepositoryImpl(dataSource)
    }

    def "Successfully insert a new user in the database"() {
        given: "a user"
        def user = new User(username: 'testUser', password: 'myPassword', firstName: 'myName',
                lastName: 'myLastName', email: '[email protected]')

        when: "calling the insert method"
        def result = repository.insert(user)

        then: "the user is saved"
        def dbUser = sql.firstRow("SELECT * FROM EXPENSE_TRACKER.USERS WHERE USERNAME = ?", [user.username])
        assert dbUser != null
        assert dbUser.get('ID') == result
        assert dbUser.get('USERNAME') == user.username
        assert dbUser.get('PASSWORD') == user.password
        assert dbUser.get('FIRSTNAME') == user.firstName
        assert dbUser.get('LASTNAME') == user.lastName
        assert dbUser.get('EMAIL') == user.email
    }
}

Класс TestContainersSpec помимо самого контейнера использует создание повторно используемой сети, чтобы я мог использовать тот же контейнер. Мне нужно было найти способ настроить мой Datasource, и я решил смоделировать его на основе экземпляра Sql, показанного ниже. Поскольку это интеграционный тест, я знаю, что это не тот путь (издевательство), но я посмотрю, как это изменить позже.

class TestContainersSpec extends Specification {

    @Shared
    public static MySQLContainer mySQL

    public static Network network = createReusableNetwork('expense-network')

    @Shared
    Sql sql

    @Shared
    DataSource dataSource

    def setup() {
        mySQL = new MySQLContainer("mysql:8.0.28")
                .withExposedPorts(3306)
                .withDatabaseName('EXPENSE_TRACKER')
                .withUsername('root')
                .withPassword('test')
                .withNetwork(network)
                .withReuse(true)
                .withStartupTimeout(Duration.ofMinutes(3))
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("mySQL")))
        mySQL.start()

        dataSource = Mock()
        sql = Sql.newInstance(mySQL.jdbcUrl, mySQL.username, mySQL.password, mySQL.driverClassName)
        dataSource.connection >> sql.connection
        createLocalDb(sql)
    }

    def createLocalDb(Sql sql) {
        ['cleanup.sql', 'schema.sql'].collect{ path ->
            getClass().getResource("/$path").text.trim()
        }.each { queries ->
            queries.tokenize(";").each{
                try {
                    sql.execute(it)
                } catch(Exception e) {
                    println e.message
                }
            }
        }
        println 'Local database ready'
    }

    static Network createReusableNetwork(String name) {
        String id = DockerClientFactory.instance().client().listNetworksCmd().exec().stream()
                .filter(network -> network.getName().equals(name)
                        && network.getLabels() == DockerClientFactory.DEFAULT_LABELS)
                .map(com.github.dockerjava.api.model.Network::getId)
                .findFirst()
                .orElseGet(() -> DockerClientFactory.instance().client().createNetworkCmd()
                        .withName(name)
                        .withOptions(['mtu': '1350'])
                        .withCheckDuplicate(true)
                        .withLabels(DockerClientFactory.DEFAULT_LABELS)
                        .exec().getId())

        return new Network() {
            @Override
            String getId() {
                return id
            }

            @Override
            void close() {
            }

            @Override
            Statement apply(@NotNull Statement statement, @NotNull Description description) {
                return statement
            }
        }
    }
}

Однако, когда я выполняю свой тест, я получаю следующую ошибку:

May 19, 2024 2:22:04 PM groovy.sql.Sql$AbstractQueryCommand execute
WARNING: Failed to execute: SELECT * FROM EXPENSE_TRACKER.USERS WHERE USERNAME = ? because: No operations allowed after connection closed.
May 19, 2024 2:22:04 PM groovy.sql.Sql$AbstractQueryCommand execute
WARNING: Failed to execute: SELECT * FROM EXPENSE_TRACKER.USERS WHERE USERNAME = ? because: No operations allowed after connection closed.

No operations allowed after connection closed.
java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed.
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:74)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:73)
    at com.mysql.cj.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:1610)
    at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:4808)
    at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:4786)
    at groovy.sql.Sql.getAbstractStatement(Sql.java:4625)
    at groovy.sql.Sql.getPreparedStatement(Sql.java:4640)
    at groovy.sql.Sql.getPreparedStatement(Sql.java:4729)
    at groovy.sql.Sql.access$1000(Sql.java:234)
    at groovy.sql.Sql$PreparedQueryCommand.runQuery(Sql.java:4925)
    at groovy.sql.Sql$AbstractQueryCommand.execute(Sql.java:4856)
    at groovy.sql.Sql.rows(Sql.java:2050)
    at groovy.sql.Sql.rows(Sql.java:1980)
    at groovy.sql.Sql.rows(Sql.java:1824)
    at groovy.sql.Sql.firstRow(Sql.java:2295)
    at com.expense.tracker.repository.UserRepositoryImplSpec.Successfully insert a new user in the database(UserRepositoryImplSpec.groovy:27)
Caused by: com.mysql.cj.exceptions.ConnectionIsClosedException: No operations allowed after connection closed.
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:104)
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:149)
    at com.mysql.cj.NativeSession.checkClosed(NativeSession.java:756)
    at com.mysql.cj.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:556)
    at com.mysql.cj.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:1539)

Я не вижу причины, мое соединение закрывается. Можете ли вы предложить мне решение?

sql закрывается после инициализации базы данных, затем для вставки используется репозиторий, а затем снова используется sql для чтения базы данных... id попробуйте использовать репозиторий для обратного чтения пользователя, или, если это не работает, создайте другой репозиторий с тем же источником данных для чтения поддержать пользователя

Perdi Estaquel 19.05.2024 14:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я всегда рекомендую использовать поддержку JDBC Testcontainer при попытке протестировать реальную базу данных при весенней загрузке. Просто определите jdbc:tc:mysql:8.0.36:///databasename как URL-адрес источника данных.

Вы также можете позволить ему выполнить скрипт инициализацииjdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql

Другие вопросы по теме