Spring Boot + Hibernate + Google Cloud Sql + App Engine

Olá galera. Estou tendo problemas para configurar a conexão na minha aplicação com o Google Cloud Sql (utilizando as tecnologias destacadas no título). Eu gostaria de poder permitir que o Spring fosse o responsável pelo DataSource, EntityManagerFactory e Transaction.

Por isso, configurei a classe a seguir e ela funciona bem quando estou executando a aplicação localmente, ou seja, passando configurações para conectar no meu banco de dados MySql local. Porém, ao alterar a configuração do DataSource para conectar no Google Cloud Sql, surje o erro: java.lang.ClassNotFoundException: com.mysql.jdbc.GoogleDriver

Seguem meus arquivos:

Configuração

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
public class BancoDeDadosConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private LocalContainerEntityManagerFactoryBean entityManagerFactory;

    /**
     * Configurações lidas do arquivo application.properties, através do objeto env
     */
    @Bean
    public DataSource dataSource() throws ClassNotFoundException {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.GoogleDriver");
        dataSource.setUrl("jdbc:google:mysql://urlMeuBancoNaNuvem:us-central1:meuBanco/nomeDaDatabase");
        dataSource.setUsername("meuUsuario");
        dataSource.setPassword("minhaSenha");

        return dataSource;
    }

    /**
     * Declarando entidate manager factory do JPA
     */

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();

        entityManagerFactory.setDataSource(dataSource);

        // escaneando pacote atras de classes anotadas
        entityManagerFactory.setPackagesToScan(" ... meuPacote .. ");

        //vendor
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactory.setJpaVendorAdapter(vendorAdapter);

        //propriedades do Hibernate
        Properties propriedades = new Properties();
        propriedades.put("hibernate.dialect", "org.hibernate.dialect.MySQL57Dialect");
        propriedades.put("hibernate.show_sql", "true");
        propriedades.put("hibernate.hbm2ddl.auto", "update");

        entityManagerFactory.setJpaProperties(propriedades);

        return entityManagerFactory;
    }

    /**
     * Transação
     */
    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());

        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

appengine-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">

    <version>1</version>
    <threadsafe>true</threadsafe>
    <runtime>java8</runtime>

    <use-google-connector-j>true</use-google-connector-j>

    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>

</appengine-web-app>

application.properties
arquivo vazio

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.meuProjeto</groupId>
<artifactId>App</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>

<name>App</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>

    <appengine.sdk.version>1.9.63</appengine.sdk.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>

        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.google.appengine</groupId>
        <artifactId>appengine-api-1.0-sdk</artifactId>
        <version>${appengine.sdk.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.6</version>
    </dependency>

    <dependency>
        <groupId>com.google.cloud.sql</groupId>
        <artifactId>mysql-socket-factory-connector-j-6</artifactId>
        <version>1.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.2.8.Final</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <!--configuração para execução local-->
        <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>appengine-maven-plugin</artifactId>
            <version>1.3.1</version>
        </plugin>

        <!--configuração para depuração local-->
        <!--<plugin>-->
            <!--<groupId>com.google.appengine</groupId>-->
            <!--<artifactId>appengine-maven-plugin</artifactId>-->
            <!--<version>${appengine.sdk.version}</version>-->
            <!--<configuration>-->
                <!--<jvmFlags>-->
                    <!--<jvmFlag>-Xdebug</jvmFlag>-->
                    <!--<jvmFlag>-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n</jvmFlag>-->
                <!--</jvmFlags>-->
            <!--</configuration>-->
        <!--</plugin>-->

    </plugins>
 </build>
</project>

Alguém poderia me ajudar?

Obrigado.

Me parece que faltam algumas dependências no projeto. Não li por completo, mas olha esse link aqui.

https://cloud.google.com/appengine/docs/standard/java/cloud-sql/using-cloud-sql-mysql#Java_Connect_to_your_database

<!-- Driver injected at runtime by the use of <use-google-connector-j> in appengine-web.xml -->
<dependency> <!-- Only used locally -->
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.42</version>  <!-- v5.x.x is for production, v6.x.x EAP X DevAPI -->
</dependency>
<dependency>
  <groupId>com.google.cloud.sql</groupId>
  <!-- If using MySQL 6.x driver, use mysql-socket-factory-connector-j-6 instead -->
  <artifactId>mysql-socket-factory</artifactId>
  <version>1.0.5</version>
</dependency>

não deixe de conferir a versão do java e tudo mais que você estiver usando se for isso mesmo.

Me desculpe, não tinha visto o seu pom inteiro.

Olá Filipe. Tranquilo … cara, passei o dia tentando resolver isso mas não consegui avançar.

Aqui… to dando uma olhada nisso mas imagino que tenha alguma forma mais simples de configurar esse datasource via application.yml ou prop

Tem alguma necessidade de configurar no java?

Não estou utilizando o arquivo application.yml - no lugar, existe o appengine-web.xml

Não sei se daria para configurar nele, tipo <bean xxxxx>

Entendi. Então vamos voltar ao problema inicial. O ClassLoader não está encontrando essa classe com.mysql.jdbc.GoogleDriver.

Está usando qual IDE? Tem como você ver se ela está nas suas dependencias?

Já deu uma olhada nesse repositório?

Blz. Estou utilizando a Intellij.

Sobre a classe com.mysql.jdbc.GoogleDriver, ela não aparece na dependência. Pelo que entendi, a anotação <use-google-connector-j>true</use-google-connector-j> no arquivo appengine-web.xml indica que esta classe será injetada quando a aplicação for executada.

Porém, como estou tentando criar um Bean, quando o Spring Boot inicia por alguma razão ele não rola.

O que eu gostaria de fazer é não ter que ficar criando e gerenciado a Persistence.CreateEntityManagerFactory(“nome unidade”); e as transações.

Achei umas coisas interessantes aqui, podem ajudar.

Assim… O Spring Cloud é um projeto maior, se você estiver iniciando o projeto acho que vale uma olhada. Se o projeto já estiver em andamento e com arquitetura definida acho que não.

Interessante. Vou estudar com mais calma. Sim, projeto já esta em andamento. Para complementar o cenário, se eu remover a classe BancoDeDadosConfig e criar o arquivo persistence.xml na pasta resources/META-INF o projeto executa.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>

<persistence
        xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
        version="1.0">

    <persistence-unit name="meuBD">
        <class>com.projeto.ga</class>

        <!-- Ambiente local -->
        <!-- <properties>-->
           <!-- <property name="javax.persistence.jdbc.user" value="user"/>-->
           <!-- <property name="javax.persistence.jdbc.password" value="senha"/>-->
           <!-- <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>-->
           <!-- <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/bancoDeDados"/>-->

           <!-- <property name="hibernate.show_sql" value="true"/>-->
           <!-- <property name="hibernate.format_sql" value="true"/>-->
           <!-- <property name="hibernate.hbm2ddl.auto" value="update"/>-->
           <!-- <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>-->
       <!--  </properties>-->


        <!-- Ambiente cloud -->
        <properties>
            <property name="javax.persistence.jdbc.user" value="user"/>
            <property name="javax.persistence.jdbc.password" value="senha"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.GoogleDriver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:google:mysql://bancoNaNuvem:us-central1:bancoNaNuvem-bd/bancoDeDados"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL57Dialect"/>
        </properties>

    </persistence-unit>

</persistence>

Um exemplo de classe DAO

@Repository
 public class PaisDao {

    public void salva(Pais pais) {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("meuBD");
        EntityManager manager = factory.createEntityManager();

        manager.getTransaction().begin();
        manager.persist(pais);
        manager.getTransaction().commit();

        manager.close();
        factory.close();
    }
}

Mas eu não queria ficar gerenciando o manager e a transação. Neste cenário, tema alguma idéia de onde eu poderia configurar o dataSource e etc… ?

Se vc já esta usando o spring data jpa vc tem tanto o JpaRepository quando o CrudRepository para extender. Eles já tem métodos pra fazer as operações básicas com gerenciamento da transação.

https://docs.spring.io/spring-data/jpa/docs/2.1.0.M1/reference/html/#repositories

Eu não conheço estas classes, vou precisar estudá-las … mas eu gostaria que meu DAO pudesse ficar assim:

@Repository
@Transactional
public class PaisDao {

    @Autowired
    private EntityManager manager;

    public void salva(Pais pais) {
        manager.persist(pais);
    }

}

Você tem essa necessidade? Porque o código com o CrudRepository fica mais simples, muitas vezes não precisa de nenhuma implementação. Você apenas cria uma interface e os métodos pra salvar, atualizar e deletar já estão prontos. Juntamente com vários para consulta…

@RepositoryRestResource(collectionResourceRel="erros", path="erros")
public interface ErrosRepository extends JpaRepository<Erro, Long> {

}

Nesse caso eu ainda faço mais… o RepositoryRestResource já gera uma api rest associada ao repositório.

Imagino que uma vez configurado o DataSource no GCloud vai tudo funcionar como no spring normal… isso aí iria agilizar um projeto. Mas como você disse, se o projeto está em andamento e a necessidade é uma classe exatamente do jeito q você postou temos que olhar outra coisa.

Agora… reparei um erro meio conceitual no seu código… Você escreve

    @Autowired
    private DataSource dataSource;

    /**
     * Configurações lidas do arquivo application.properties, através do objeto env
     */
    @Bean
    public DataSource dataSource() throws ClassNotFoundException {
        ...
    }

na mesma classe… você entende que na parte de baixo está criando o bean e a de cima injetando ele?

O certo para você injeta-lo no entity manager factory bean seria

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
    ....
    }

    .....
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactoryBean entityManagerFactory) {
    ....
    }

sem a necessidade de um Autowired e uma propriedade na classe. Quando você coloca parâmetro nos beans o spring tenta injeta-los pelo tipo.

Hum, valeu pela dica. Já ajustei aqui. Cara, obrigado mesmo pela atenção brother mas eu tô um bagaço agora rsrsrs

Depois irei fazer novos testes e por hora, vou deixar o projeto com o arquivo persistence.xml e ir gerenciando as transações.

Obrigado e boa noite ")

Vai lá…

Repara o que esses caras arrumaram… Meio que deployaram usando o driver padrão, parece que a url muda, fica como as padrão:

jdbc:mysql://google/db_name?cloudSqlInstance=<instance_connection_name>&socketFactory=com.google.cloud.sql.mysql.SocketFactory

@thimfont

Não deixa de olhar esses links. Você consegue colocar o application.yml (ou application.properties) no projeto sim. Descobri uns links intessantes pra você.

https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/helloworld-springboot (esse link aqui tem um projeto spring-boot funcional, pronto pra deployar no gcloud)
https://cloud.google.com/java/samples
https://codelabs.developers.google.com/codelabs/cloud-app-engine-springboot/index.html?index=..%2F..%2Findex#0