Ваадин: Как мне отобразить данные из базы данных MySQL? - PullRequest
2 голосов
/ 24 января 2020

Я занимаюсь разработкой приложения Vaadin Flow (версия 14.1), и у меня возникла эта проблема, я не могу подключить его напрямую к базе данных MySQL.

Я настроил JDB C В связи с Maven я также создал одноэлементный класс, который я называю Datasource, где я храню свои значения и методы. Однако сейчас у меня есть только один, когда я тестирую это, вот что я хочу сделать:

  • Нажмите на кнопку в приложении и обновите метку

Вот слушатель нажатия кнопки:

button.addClickListener(click -> {
        label.setText(Datasource.getInstance().getUsername());
    });

Вот метод класса источника данных:

public String getUsername() {
    String username = "QUERY-FAILED";

    try {
        start();
        statement = conn.createStatement();
        ResultSet rs = statement.executeQuery("select * from names");

        rs.next();
        username = rs.getString(1);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close();
    }

    return username;
}

Но метка не обновляется, если я комментирую блок try, она обновляется до QUERY- FAILED - строка, которую я поставил для проверки, если она не удалась, но если она не закомментирована, метка просто остается прежней.

Я также попытался добавить метод main в класс Datasource и запустить его как Java приложение, и метод работает нормально, он возвращает строку с именем пользователя. Я предполагаю, что застрял где-то между соединением с приложением vaadin. Кроме того, если я пытаюсь получить имя пользователя String в моем приложении vaadin при запуске приложения (а не с прослушивателем щелчков), я получаю длинный стек ошибок с источником данных, указывающим здесь исключение nullpointerexception:

statement = conn.createStatement();

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 24 января 2020

Вы должны проверить, действительно ли ResultSet rs имеет результаты. При вызове rs.next() посмотрите, не возвращается ли оно. Как проверить, является ли ResultSet пустым

public String getUsername() {
    String username = "QUERY-FAILED";

    try {
        start();
        statement = conn.createStatement();
        ResultSet rs = statement.executeQuery("select userName from names");

        if(rs.next() != false){
            username = rs.getString(1);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close();
    }

    return username;
}

Еще одна вещь, которую я заметил, состоит в том, что из запроса select * from names нельзя быть уверенным, что атрибут userName является первым столбцом, который вы читаем с rs.getString(1). Обязательно напишите свой запрос более точно, чтобы избежать труднодоступных ошибок: select userName from names; Сделайте это, даже если в таблице только один столбец, потому что, если кто-то добавит другой столбец? это может сломать ваше приложение.

1 голос
/ 24 января 2020

Я не могу обнаружить никаких проблем с вашим кодом. Но я могу предоставить вам полный рабочий пример приложения для сравнения

Мой пример приложения идет в соответствии с принципами, изложенными в коде вашего Вопроса. Vaadin Button выполняет запрос к базе данных, используя объект DataSource из таблицы имен пользователей. Значение из первой найденной строки отображается в виджете Vaadin Label на веб-странице.

enter image description here

Это приложение было создано и работает с Vaadin 14.1.5 с использованием аромата «Простой Java сервлет» стартового проекта , предоставленного сайтом Vaadin.com . Запуск на MacOS Mojave с помощью встроенного веб-контейнера Jetty.

Моими единственными изменениями в их POM-файле Maven было изменение на Java версию 13 и добавление зависимости для H2 Database Engine , чтобы сделать это самостоятельно Пример использования базы данных в памяти.

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
    </dependency>

Я использовал перехват для запуска Vaadin, чтобы установить sh my DataSource объект и инициализировать базу данных. Следуя инструкции , вложенной в папку resources, я создал папки META-INF> services. Согласно средству Java SPI , я создал файл с именем com.vaadin.flow.server.VaadinServiceInitListener, содержащий одну строку для указания имени моего класса, который реализует интерфейс, названный в имени этого файла:

work.basil.example.ApplicationServiceInitListener

То есть мой класс ApplicationServiceInitListener реализует интерфейс Vaadin VaadinServiceInitListener. Мой класс будет автоматически создан, и его метод будет вызван с помощью этого Java SPI, когда запустится мое веб-приложение Vaadin.

Мой ApplicationServiceInitListener класс:

package work.basil.example;

import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import org.h2.jdbcx.JdbcDataSource;

public class ApplicationServiceInitListener implements VaadinServiceInitListener
{
    @Override
    public void serviceInit ( ServiceInitEvent serviceInitEvent )
    {
        System.out.println( "DEBUG Running `serviceInit` of " + this.getClass().getCanonicalName() );

        // Database work.
        prepareDataSource();
        App.INSTANCE.provideDatabase().initializeDatabase();
    }

    private void prepareDataSource ( )
    {
        JdbcDataSource ds = new JdbcDataSource();
        ds.setURL( "jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1" );
        ds.setUser( "scott" );
        ds.setPassword( "tiger" );
        App.INSTANCE.rememberDataSource( ds );
    }
}

Этот класс вызывает мой класс App, который действует как своего рода указатель службы . Разработанный как singleton через enum .

package work.basil.example;

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

public enum App
{
    INSTANCE;

    // -------|  DataSource  |---------------------------------
    private DataSource dataSource;

    public DataSource provideDataSource ( )
    {
        return this.dataSource;
    }

    public void rememberDataSource ( DataSource dataSource )
    {
        this.dataSource = Objects.requireNonNull( dataSource );
    }


    // -------|  Database  |---------------------------------
    private Database database;

    public Database provideDatabase ( )
    {
        return new Database();
    }
}

Этот класс вызывает мой Database класс. В реальной работе Database будет интерфейсом с различными конкретными реализациями для тестирования и развертывания. Я проигнорировал это здесь для демонстрационных целей.

package work.basil.example;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Database
{
    public String getFirstUserName ( )
    {
        String userName = "QUERY-FAILED";

        String newline = "\n";
        StringBuilder sql = new StringBuilder();
        sql.append( "SELECT name_ from  user_ ; " ).append( newline );
        System.out.println( "sql = " + sql );
        try (
                Connection conn = App.INSTANCE.provideDataSource().getConnection() ;
                Statement statement = conn.createStatement() ;
                ResultSet resultSet = statement.executeQuery( sql.toString() ) ;
        )
        {
            while ( resultSet.next() )
            {
                userName = resultSet.getString( "name_" );
                break; // Go no further. We need only the first row found.
            }
        }
        catch ( SQLException e )
        {
            e.printStackTrace();
        }

        return userName;
    }

    public void initializeDatabase ( )
    {
        System.out.println( "DEBUG Running `initializeDatabase` of " + this.getClass().getCanonicalName() );
        String newline = "\n";

        // Create table.
        StringBuilder sql = new StringBuilder();
        sql.append( "CREATE TABLE user_ ( " ).append( newline );
        sql.append( "pkey_ IDENTITY NOT NULL PRIMARY KEY , " ).append( newline );  // `identity` = auto-incrementing long integer.
        sql.append( "name_ VARCHAR NOT NULL " ).append( newline );
        sql.append( ") " ).append( newline );
        sql.append( ";" ).append( newline );
        System.out.println( "sql = " + sql );
        try (
                Connection conn = App.INSTANCE.provideDataSource().getConnection() ;
                Statement statement = conn.createStatement() ;
        )
        {
            statement.executeUpdate( sql.toString() );
        }
        catch ( SQLException e )
        {
            e.printStackTrace();
        }
        System.out.println("DEBUG Finished `CREATE TABLE` statement.");

        // Populate table.
        sql = new StringBuilder();
        sql.append( "INSERT INTO user_ ( name_ ) " ).append( newline );
        sql.append( "VALUES " ).append( newline );
        sql.append( "( 'Alice' ) , " ).append( newline );
        sql.append( "( 'Bob' ) , " ).append( newline );
        sql.append( "( 'Carol' ) " ).append( newline );
        sql.append( ";" ).append( newline );
        System.out.println( "sql = " + sql );
        try (
                Connection conn = App.INSTANCE.provideDataSource().getConnection() ;
                Statement statement = conn.createStatement() ;
        )
        {
            int rowsAffected = statement.executeUpdate( sql.toString() );
            System.out.println( "DEBUG Inserted rows into name_ table: " + rowsAffected );
        }
        catch ( SQLException e )
        {
            e.printStackTrace();
        }
        System.out.println("DEBUG Finished `INSERT` statement.");
    }
}

И, наконец, класс MainView. Я отключаю аннотацию @PWA, так как мы не используем эту функцию для прогрессивных веб-приложений.

package work.basil.example;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;

/**
 * The main view contains a button and a click listener.
 */
@Route ( "" )
//@PWA ( name = "Project Base for Vaadin", shortName = "Project Base" )
public class MainView extends VerticalLayout
{
    private Label label;
    private Button button;

    public MainView ( )
    {
        // Widgets
        this.label = new Label( "User: ?" );
        this.button = new Button(
                "Get user" ,
                event -> {
                    Notification.show( "Getting user." );
                    String userName = App.INSTANCE.provideDatabase().getFirstUserName();
                    this.label.setText( "User: " + userName );
                }
        );
        add( button );

        // Arrange
        this.add( label , button );
    }
}
...