>

저는 Java API를 작성 중이며 MySQL을 사용하고 있습니다. 스레드 안전 읽고 데이터베이스에 쓰려면 가장 좋은 방법은 무엇입니까?

예를 들어, 다음 createUser 를  방법 :

// Create a user
// If the account does not have any users, make this
// user the primary account user
public void createUser(int accountId) {
    String sql =
            "INSERT INTO users " +
            "(user_id, is_primary) " +
            "VALUES " +
            "(0, ?) ";
    try (
            Connection conn = JDBCUtility.getConnection();
            PreparedStatement ps = conn.prepareStatement(sql)) {
        int numberOfAccountsUsers = getNumberOfAccountsUsers(conn, accountId);
        if (numberOfAccountsUsers > 0) {
            ps.setString(1, null);
        } else {
            ps.setString(1, "y");
        }
        ps.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// Get the number of users in the specified account
public int getNumberOfAccountsUsers(Connection conn, int accountId) throws SQLException {
    String sql =
            "SELECT COUNT(*) AS count " +
            "FROM users ";
    try (
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery()) {
        return rs.getInt("count");
    }
}

Say source A는 createUser 를 호출합니다. 계정 ID 100에 0 명의 사용자가 있음을 읽었습니다. 그런 다음 소스 B는 createUser 를 호출합니다. 소스 A가 업데이트를 실행하기 전에 소스 B는 계정 ID가 0 명의 사용자임을 읽었습니다. 결과적으로 소스 A와 소스 B 모두 기본 사용자를 만들었습니다.

이 상황을 어떻게 안전하게 구현할 수 있습니까?

  • 답변 # 1

    질문 개요

    먼저, 질문은 스레드 안전성과 관련이 없으며 더 잘 최적화 될 수있는 트랜잭션 및 코드와 관련이 있습니다.

    이 내용을 올바르게 읽으려면 첫 번째 사용자를 기본 사용자로 설정하십시오. 아마이 접근법을 전혀 사용하지 않을 것입니다 (즉, 첫 번째 사용자에 대해 is_primary 행을 가짐). 그렇다면 Java 응용 프로그램에 해당 조건을 전혀 포함시키지 않을 것입니다. 사용자를 생성 할 때마다 조건부로 평가 될뿐만 아니라 데이터베이스를 불필요하게 호출하게됩니다. 대신 나는 이렇게 리팩토링 할 것이다.

    코드

    먼저 식탁이 이런 식인지 확인하십시오.

    CREATE TABLE `users` (
         user_id INT NOT NULL AUTO_INCREMENT,
         is_primary CHAR(1),
         name VARCHAR(30),
         PRIMARY KEY (user_id)
    );
    
    

    즉, user_id는 auto_increment 여야합니다.  그리고 not null . 나는 name 를 포함  아래 요점을 설명하는 데 도움이되기 때문에 열 ( user_id 이외의 사용자 테이블의 다른 열 대신 사용할 수 있음)  그리고 is_primary ). 나는 또한 user_id 를했다  아마도 기본이기 때문입니다.

    현재 테이블 만 수정하려면 다음과 같이하십시오.

    ALTER TABLE `users` MODIFY COLUMN `user_id` INT not null auto_increment;
    
    

    그런 다음 행을 삽입 할 때마다 첫 번째 행인지 확인하고 그에 따라 업데이트되도록 트리거를 만듭니다.

    delimiter |
        CREATE TRIGGER primary_user_trigger 
        AFTER INSERT ON users
          FOR EACH ROW BEGIN
            IF NEW.user_id = 1 THEN
               UPDATE users SET is_primary = 'y' where user_id = 1;
            END IF;
          END;
    | delimiter ;
    
    

    그 시점에서 당신은 단지 createUser 를 가질 수 있습니다  새 레코드를 데이터베이스에 삽입하는 메소드로user_id 또는 기본 필드를 전혀 지정할 필요가 없습니다. 테이블이 다음과 같다고 가정 해 봅시다.

    createUser 메소드는 본질적으로 다음과 같습니다.

    // Create a user
    // If the account does not have any users, make this
    // user the primary account user
    public void createUser(String name) {
        String sql =
                "INSERT INTO users (name) VALUES(?)"
        try (
                Connection conn = JDBCUtility.getConnection();
                PreparedStatement ps = conn.prepareStatement(sql));
                ps.setString(1, name);
                ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    

    사용자 테이블의 두 배는 다음과 같습니다.

    |    user_id   |   is_primary   |   name   |
    +--------------+----------------+----------+
    |      1       |       y        |   Jimmy  |
    ---------------+----------------+----------+
    |      2       |       null     |   Cindy  |
    
    

    그러나 ... 그러나 위에서 언급 한 해결책이 원래의 질문에서 제안 된 것보다 낫다고 생각하지만 여전히 이것을 처리하는 최적의 방법이라고 생각하지 않습니다. 제안하기 전에 프로젝트에 대해 더 알아야합니다. 첫 번째 사용자 기본은 무엇입니까? 기본 사용자 란 무엇입니까? 기본 사용자가 하나만있는 경우 기본 사용자를 수동으로 설정할 수없는 이유는 무엇입니까? 관리 콘솔이 있습니까? 기타

    질문이 예시라면 ...

    따라서 질문이 단순히 트랜잭션 관리에 대한 더 큰 질문을 설명하는 데 사용 된 예인 경우 거짓 연결에 자동 커밋을 사용하여 수동으로 트랜잭션을 관리 할 수 ​​있습니다. 자동 커밋에 대한 자세한 내용은 Oracle JDBC Java 설명서를 참조하십시오. 또한 위에서 언급 한 것처럼 특정 작업에 따라 테이블/행 수준 잠금을 변경할 수 있지만이 문제에 대한 비현실적인 해결책이라고 생각합니다. 선택을 하위 쿼리로 포함시킬 수도 있지만 다시는 나쁜 연습에 반창고를 사용하는 것입니다.

    기본 사용자 패러다임을 완전히 바꾸고 싶지 않다면 이것이 가장 효율적이고 체계적인 방법이라고 생각합니다.

  • 답변 # 2

    is_primary 를 설정하는 조건을 포함하는 것이 좋습니다  하나의 삽입 쿼리에서 값 :

    public void createUser(int accountId) {
        String sql = "INSERT INTO users (user_id, is_primary) " +
                     "SELECT 0, if(COUNT(*)=0, 'y', null) " +
                     "FROM users";
       //....
    }
    
    

    따라서 멀티 스레딩 환경에서 코드를 실행하는 것이 안전하며 getNumberOfAccountsUsers 를 제거 할 수 있습니다  방법.

    와 동시에 insert 의 가시성에 대한 의심을 없애기 위해  설명서에 명시된 바와 같이 (@tsolakp 주석 덕분에) :

    와이즈 비즈 당신은 같은

    The results of a concurrent INSERT may not be visible immediately.

    를 사용할 수 있습니다  삽입 문에 대한 객체로 MySql 서버가 순차적으로 실행합니다.

    또는

    Connection 에 고유 인덱스 사용  열, ( is_primary 라고 가정합니다.  그리고 y  가능한 가치입니다. 여러 개의 null 참고  값은 모든 MySql 엔진에서 허용되므로) 고유 제약 조건 위반의 경우 null 를 다시 실행하면됩니다.  질문. 이 고유 한 인덱스 솔루션은 기존 코드에서도 작동합니다.

    insert

  • 이전 javascript - 새 탭에서 PDF 데이터 객체 열기
  • 다음 vb.net - LINQ 요청으로 JSON 문자열에서 데이터 추출