Java SQL H2:如何从我的 DAO 获取自动递增 ID?

问题描述 投票:0回答:1

我在 Java 应用程序(使用 H2)中的自动增量列方面遇到了一些问题。主要是,我无法找出从 DAO 访问 ID 的最佳方式:

相关代码部分是: 我有一个表

PEOPLE
,其中包含自动递增
ID
列和字符串
NAME
列:

create table PEOPLE (
    ID INTEGER auto_increment,
    NAME CHARACTER VARYING(50)
);

PeopleDAO.java

public class PeopleDAO {
    public static ObservableList<People> getPeople() throws SQLException, ClassNotFoundException {
        String sql = "SELECT * FROM PEOPLE";
        try {
            PreparedStatement statement = getConnection().prepareStatement(sql);
            ResultSet rs = DBHelpers.executeQuerry(statement);
            return getPeopleList(rs);
        } catch (SQLException e {
            throw e
        }
    }

    public static ObservableList<Person> getPeopleList(ResultSet rs) throws SQLException, ClassNotFoundException {
        ObservableList<Person> peopleList = FXCollections.observableArrayList();
        while (rs.next()) {
             Person person = new Person(
                  rs.getString("NAME");
             peopleList.add(person);
        }
        return peopleList;
    }

    public static void insertPerson(String name) throws SQLException, ClassNotFoundException {
        String sql = "INSERT INTO PEOPLE (NAME) VALUES (?)";
        try {
             PreparedStatement statement = getConnection().prepareStatement(sql);
             statement.setString(1, name);
             DBHelpers.executeUpdate(statement);
        } catch (SQLException | ClassNotFoundException e) {
             throw e;
        }
   }
}

DBHelpers.java 这有我的驱动程序、URL、连接等。

getConnection()
返回 Connection 对象,并且
executeQuery()
exeucteUpdate()
执行他们所说的操作(分别是statement.executeQuery() 和statement.executeUpdate())

Person.java

public class Person {
    String name;
    
    public Person(String name) {
        this.name = name;
    }
}

我的问题是如何从

ID
对象访问
Person
列?我尝试添加该字段

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
int ID;

to

Person
,并为它创建一个 getter,但它为第一个对象返回 0,而不是 1,我认为这与它是一个零索引的对象列表有关。如果它是一个非递增列,我只需在
getPeopleList()
中创建 Person 对象时添加 ID,但由于 ID 是自动分配的,我不确定如何执行此操作。

如果需要,我可以发布更多我的代码。

java sql jdbc h2
1个回答
0
投票

首先让应用程序运行的基础知识,使用虚拟数据且没有数据库。

让我们定义

Person
类,其中包含两个字段
id
name

如果类的主要目的是透明地通信浅层不可变数据,请将类定义为记录

public record Person( int id , String name ) { }

让我们使用常见的 Repository 模式。我们定义一个接口,定义用于存储和检索对象的方法。

public interface Repository
{
    public Collection < Person > allPeople ( );

    public Optional < Person > addPerson ( String name );
}

进行一个虚假的实现,只是为了让我们的前几个部分正常工作。

public class RepositoryBogus implements Repository
{
    private Collection < Person > persons =
            new ArrayList <>(
                    List.of(
                            new Person( 1 , "Alice" ) ,
                            new Person( 2 , "Bob" ) ,
                            new Person( 3 , "Carol" )
                    ) );

    @Override
    public List < Person > allPeople ( )
    {
        return List.copyOf( this.persons );
    }

    @Override
    public Optional < Person > addPerson ( final String name )
    {
        Person person = new Person( ( this.persons.size( ) + 1 ) , name );
        this.persons.add( person );
        return Optional.of( person );
    }
}

编写一个应用程序来练习这些课程。

public class App
{
    public static void main ( String[] args )
    {
        App app = new App( );
        app.demo( );
    }

    private void demo ( )
    {
        Repository repository = new RepositoryBogus( );

        // Before
        Collection < Person > persons = repository.allPeople( );
        System.out.println( "persons = " + persons );

        // After
        Optional < Person > optionalDavis = repository.addPerson( "Davis" );
        System.out.println( "optionalDavis = " + optionalDavis );  // Verify that Davis got an id value.
        System.out.println( "repository.allPeople() = " + repository.allPeople( ) );  // Verify that Davis and his id were stored in database successfully.
    }
}

我们的

addPerson
方法会在失败时返回
Optional
。我们必须测试
Optional
来验证是否成功。

运行时,我们看到成功。我们添加“Davis”作为我们的第四个人,返回一个对象,其

id
字段具有预期的值
4

persons = [Person[id=1, name=Alice], Person[id=2, name=Bob], Person[id=3, name=Carol]]
optionalDavis = Optional[Person[id=4, name=Davis]]
repository.allPeople() = [Person[id=1, name=Alice], Person[id=2, name=Bob], Person[id=3, name=Carol], Person[id=4, name=Davis]]

现在我们可以正确实现

Repository
来使用 H2 数据库引擎 数据库。

我们最初的实施是最小的:

public class RepositoryH2 implements Repository
{
    @Override
    public Collection < Person > allPeople ( )
    {
        return List.of( );
    }

    @Override
    public Optional < Person > addPerson ( final String name )
    {
        return Optional.empty( );
    }
}

再次运行我们相同的应用程序代码。

persons = []
optionalDavis = Optional.empty
repository.allPeople() = []

很好,符合预期。

我们需要

DataSource
实现来保存我们的数据库凭据(用户名、密码、服务器地址、数据库产品特定设置等)。在实际工作中,我们将通过 JNDI 从命名/目录服务器获取一个。在我们的示例中,我们在与 H2 捆绑的简单实现的对象中伪造了硬编码值:
org.h2.jdbcx.JdbcDataSource

我们需要在数据库中创建一个表,然后填充它。在实际工作中,我们会使用数据库迁移工具,例如FlywayLiquibase。在我们的示例中,我们通过在 JDBC 代码中硬编码一些 SQL 来伪造它。

我们在名为

DataSource
的类上的一些静态方法中进行
DatabaseUtils
实例化和表创建/填充。我们还编写了一个方法将表转储到控制台以进行调试。

package work.basil.example.db.autoincrement;

import org.h2.jdbcx.JdbcDataSource;
import org.intellij.lang.annotations.Language;

import javax.sql.DataSource;
import java.sql.*;
import java.util.List;

// The code here is a hack to keep our example simple.
// In real work you would obtain a `DataSource` object via JNDI from a naming/directory server.
// In real work you would create tables by way of a database migration tool such as Flyway or Liquibase.
public class DatabaseUtils
{
    public static DataSource makeDatabase ( )
    {
        DataSource dataSource = DatabaseUtils.fetchDataSource( );
        DatabaseUtils.createPersonTable( dataSource );
        DatabaseUtils.populatePersonTable( dataSource );
        DatabaseUtils.dumpPersonTable( dataSource );
        return dataSource;
    }

    private static DataSource fetchDataSource ( )
    {
        JdbcDataSource dataSource = new JdbcDataSource( );  // Implementation of `DataSource` bundled with H2.
        dataSource.setURL( "jdbc:h2:mem:h2_identity_example_db;DB_CLOSE_DELAY=-1" ); // Set `DB_CLOSE_DELAY` to `-1` to keep in-memory database in existence after connection closes.
        dataSource.setUser( "scott" );
        dataSource.setPassword( "tiger" );
        return dataSource;
    }

    private static void createPersonTable ( final DataSource dataSource )
    {
        @Language ( "SQL" )
        String sql = """
                CREATE TABLE person_
                   ( 
                      id_ INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  -- ⬅ `identity` = auto-incrementing integer number.
                      name_ VARCHAR NOT NULL 
                    )
                ;
                """;
        try (
                Connection connection = dataSource.getConnection( ) ;
                Statement statement = connection.createStatement( ) ;
        )
        {
            statement.execute( sql );
        } catch ( SQLException e )
        {
            throw new RuntimeException( e );
        }
    }

    private static void populatePersonTable ( final DataSource dataSource )
    {
        @Language ( "SQL" )
        String sql = """
                INSERT INTO person_ (  name_ )
                VALUES ( ? )
                ;
                """;
        try (
                Connection connection = dataSource.getConnection( ) ;
                PreparedStatement preparedStatement = connection.prepareStatement( sql ) ;
        )
        {
            for ( String name : List.of( "Alice" , "Bob" , "Carol" ) )
            {
                preparedStatement.setString( 1 , name );
                int rowCount = preparedStatement.executeUpdate( );
                if ( rowCount != 1 ) throw new IllegalStateException( "Failed to insert " + name + " into database" );
            }
        } catch ( SQLException e )
        {
            throw new RuntimeException( e );
        }
    }

    public static void dumpPersonTable ( final DataSource dataSource )
    {
        @Language ( "SQL" )
        String sql = """
                SELECT *
                FROM person_
                ;
                """;
        try (
                Connection connection = dataSource.getConnection( ) ;
                Statement statement = connection.createStatement( ) ;
                ResultSet resultSet = statement.executeQuery( sql ) ;
        )
        {
            System.out.println( "---------|  Start dump of table person_  |--------------------" );
            while ( resultSet.next( ) )
            {
                System.out.println( new Person( resultSet.getInt( "id_" ) , resultSet.getString( "name_" ) ) );
            }
            System.out.println( "---------|  End dump of table person_  |--------------------" );
        } catch ( SQLException e )
        {
            throw new RuntimeException( e );
        }
    }
}

因此,有了

DataSource
和数据库表,我们就可以充实我们的
Repository
实现。

allPeople
方法与上面看到的
dumpPersonTable
方法基本相同。

addPerson
方法是问题的核心:如何通过 JDBC 从 Java 访问数据库引擎中生成的密钥。要实现这一点,请要求
PreparedStatement
将所有生成的密钥返回给 Java。通过在调用
Statement.RETURN_GENERATED_KEYS
准备语句时传递可选参数
Connection#prepareStatement
来实现此目的。

PreparedStatement preparedStatement = connection.prepareStatement( sql , Statement.RETURN_GENERATED_KEYS ) ;

所以这是我们的

RepositoryH2
类,实现我们的
Repository
接口。

package work.basil.example.db.autoincrement;

import org.h2.jdbcx.JdbcDataSource;
import org.intellij.lang.annotations.Language;

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

public class RepositoryH2 implements Repository
{
    // Member fields
    private final DataSource dataSource;

    // Constructors
    public RepositoryH2 ( final DataSource dataSource )
    {
        this.dataSource = Objects.requireNonNull( dataSource );
    }


    //------------|  Implement Repository  |-----------------------------------

    @Override
    public Collection < Person > allPeople ( )
    {
        @Language ( "SQL" )
        String sql = """
                SELECT *
                FROM person_
                ;
                """;
        try (
                Connection connection = dataSource.getConnection( ) ;
                Statement statement = connection.createStatement( ) ;
                ResultSet resultSet = statement.executeQuery( sql ) ;
        )
        {
            Collection < Person > persons = new ArrayList <>( );
            while ( resultSet.next( ) )
            {
                Person person = new Person( resultSet.getInt( "id_" ) , resultSet.getString( "name_" ) );
                persons.add( person );
            }
            return persons;
        } catch ( SQLException e )
        {
            throw new RuntimeException( e );
        }
    }

    @Override
    public Optional < Person > addPerson ( final String name )
    {
        @Language ( "SQL" )
        String sql = """
                INSERT INTO person_ (  name_ )
                VALUES ( ? )
                ;
                """;

        Optional < Person > personOptional = Optional.empty( );
        try (
                Connection connection = dataSource.getConnection( ) ;
                PreparedStatement preparedStatement = connection.prepareStatement( sql , Statement.RETURN_GENERATED_KEYS ) ;
        )
        {
            preparedStatement.setString( 1 , name );

            preparedStatement.executeUpdate( );
            try (
                    ResultSet rs = preparedStatement.getGeneratedKeys( ) ;
            )
            {
                while ( rs.next( ) )
                {
                    int id = rs.getInt( 1 ); // Generated key.
                    Person person = new Person( id , name );
                    personOptional = Optional.of( person );
                }
            }
        } catch ( SQLException e )
        {
            throw new RuntimeException( e );
        }
        return personOptional;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.