Java鏈接數據庫 #01# JDBC單線程適用

官方教程(包括 javase的基礎部分):JDBC Basicshtml

從新梳理、學習一下「Java鏈接數據庫」相關的內容。java

 由於最開始沒有認真學多線程和JDBC,一直在本身寫的多線程程序中維持下面的錯誤寫法:mysql

  1. 多個線程共用一個connection 
  2. connection只開不關

爲何上述作法是錯誤的呢? 能夠參看這個帖子。- - 「JDBC規範並未規定那三個對象必須是線程安全的,所以全部的JDBC廠商也不會去弄成線程安全的,正由於如此,因此就會有併發問題。」 、-- 「 並非說不能把鏈接對象弄成成員變量,只是不能將其弄成成員變量後,在多線程環境下處於共享這些對象,若是同步處理得很差,那就會產生嚴重的鏈接泄漏。爲了不這種狀況發生,僅在用時獲取鏈接,用完後立刻關掉。」 -- 「若是你對JDBC、多線程編程沒有達到很是熟練的程度,仍是老老實實地使用經典的JDBC代碼結構。」 -- 摘抄自csdn 火龍果被佔用了sql

另外,connection只開不關很容易致使鏈接失效(mysql默認保持鏈接的時間是8小時,若是這個鏈接在8小時內無人訪問的話,就會關閉這個鏈接。- -摘)數據庫

我把這些錯誤代碼放在第一小節記錄下來,做爲java鏈接數據庫的最原始版本,在這以後逐漸改良成能夠適應各類場景的正確代碼。編程

① DDL.sql多線程

DROP TABLE IF EXISTS `profile`;
CREATE TABLE `profile` (
    `profileId` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(50) NOT NULL,
    `password` VARCHAR(50) NOT NULL,
    `nickname` VARCHAR(50) NOT NULL,
    `last_online` TIMESTAMP NULL DEFAULT NULL,
    `gender` CHAR(1) NULL DEFAULT NULL,
    `birthday` TIMESTAMP NULL DEFAULT NULL,
    `location` VARCHAR(50) NULL DEFAULT NULL,
    `joined` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`profileId`),
    UNIQUE INDEX `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8;

profile表能夠基本等價爲user表來理解。併發

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -oracle

② 對應的實體類Profile.java

package org.sample.entity;

import java.sql.Timestamp;

public class Profile {
    private Long profileId;
    private String username;
    private String password;
    private String nickname;
    private Timestamp last_online;
    private Character gender;
    private Timestamp birthday;
    private String location;
    private Timestamp joined;

    public Profile() {
    }

    public Long getProfileId() {
        return profileId;
    }

    public void setProfileId(Long profileId) {
        this.profileId = profileId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Timestamp getLast_online() {
        return last_online;
    }

    public void setLast_online(Timestamp last_online) {
        this.last_online = last_online;
    }

    public Character getGender() {
        return gender;
    }

    public void setGender(Character gender) {
        this.gender = gender;
    }

    public Timestamp getBirthday() {
        return birthday;
    }

    public void setBirthday(Timestamp birthday) {
        this.birthday = birthday;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public Timestamp getJoined() {
        return joined;
    }

    public void setJoined(Timestamp joined) {
        this.joined = joined;
    }

    @Override
    public String toString() {
        return "Profile{" +
                "profileId=" + profileId +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", nickname='" + nickname + '\'' +
                ", last_online=" + last_online +
                ", gender=" + gender +
                ", birthday=" + birthday +
                ", location='" + location + '\'' +
                ", joined=" + joined +
                '}';
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

③ ConnectionFactory.java或者常說的Dbutil(錯誤代碼 ↓

package org.sample.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;

/**
 * 單線程適用,只開不關,反覆用一個 Connection
 */
public class StaticConnectionFactory {

    private static ResourceBundle rb = ResourceBundle.getBundle("org.sample.db.db-config");

    private static final String JDBC_URL = rb.getString("jdbc.url");

    private static final String JDBC_USER = rb.getString("jdbc.username");

    private static final String JDBC_PASSWORD = rb.getString("jdbc.password");

    private static Connection conn = null;

    static {
        try {
            // Class.forName("org.gjt.mm.mysql.Driver");
            // JDBC 4.0 以後(包括 JDBC 4.0)再也不須要 class.forName ,詳細查看 javaSE6 以後的 API
            conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
        } catch (SQLException e) {
            throw new RuntimeException("Error connecting to the database", e);
        }
    }

    public static Connection getConnection() {
        return conn;
    }

    public static void setAutoCommit(boolean autoCommit) throws SQLException {
        conn.setAutoCommit(autoCommit);
    }

    public static void commit() throws SQLException {
        conn.commit();
    }

    public static void rollback() throws SQLException {
        conn.rollback();
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 ④ org/sample/db/db-config.properties

jdbc.url=jdbc:mysql://***.**.**.**:3306/profiles?characterEncoding=utf8
jdbc.username=root
jdbc.password=aaaaaaaaaaa

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑤ ProfileDAO.java

package org.sample.dao;

import org.sample.entity.Profile;

import java.util.List;

public interface ProfileDAO {

    int saveProfile(Profile profile);

    List<Profile> listProfileByNickname(String nickname);

    Profile getProfileByUsername(String username);

    int updateProfileById(Profile profile);

    int updatePassword(String username, String password);

    int updateLastOnline(String username);
}

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑥ ProfileDAOImpl.java(爲了用「帶資源的try」嚴重多此一舉了。)

package org.sample.dao.impl;

import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import org.sample.dao.ProfileDAO;
import org.sample.db.StaticConnectionFactory;
import org.sample.entity.Profile;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

// NotThreadSafe
public class ProfileDAOImpl implements ProfileDAO {

    private static final Connection conn = StaticConnectionFactory.getConnection();

    @Override
    public int saveProfile(Profile profile) {
        int i = 0;
        try (
                PreparedStatement ps =
                        createPreparedStatementForSaveProfile(conn, profile);
        ) {
            i = ps.executeUpdate();
        } catch (SQLException e) {
            if (!(e instanceof MySQLIntegrityConstraintViolationException)) {
                e.printStackTrace();
            }
        }
        return i;
    }

    @Override
    public List<Profile> listProfileByNickname(String nickname) {
        List<Profile> profiles = new ArrayList<>();
        try (
                PreparedStatement ps =
                        createPreparedStatementForListProfileByNickname(conn, nickname);
                ResultSet rs = ps.executeQuery();
        ) {
            while (rs.next()) {
                Profile profile = extractProfileFromResultSet(rs);
                profiles.add(profile);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return profiles;
    }

    @Override
    public Profile getProfileByUsername(String username) {
        Profile profile = null;
        try (
                PreparedStatement ps =
                        createPreparedStatementForGetProfileByUsername(conn, username);
                ResultSet rs = ps.executeQuery();
        ) {
            if (rs.next()) {
                profile = extractProfileFromResultSet(rs);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return profile;
    }

    @Override
    public int updateProfileById(Profile profile) {
        int i = 0;
        try (
                PreparedStatement ps =
                        createPreparedStatementForUpdateProfileById(conn, profile);
        ) {
            i = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return i;
    }

    @Override
    public int updatePassword(String username, String password) {
        int i = 0;
        try (
                PreparedStatement ps =
                        createPreparedStatementForUpdatePassword(username, password);
        ) {
            i = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return i;
    }

    @Override
    public int updateLastOnline(String username) {
        int i = 0;
        try (
                PreparedStatement ps =
                        createPreparedStatementForUpdateLastOnline(username);
        ) {
            i = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return i;
    }

    private Profile extractProfileFromResultSet(ResultSet rs) throws SQLException {
        Profile profile = new Profile();
        profile.setBirthday(rs.getTimestamp("birthday"));
        profile.setJoined(rs.getTimestamp("joined"));
        profile.setLast_online(rs.getTimestamp("last_online"));
        profile.setLocation(rs.getString("location"));
        profile.setNickname(rs.getString("nickname"));
        profile.setPassword(rs.getString("password"));
        profile.setProfileId(rs.getLong("profile_id"));
        profile.setUsername(rs.getString("username"));
        if (rs.getString("gender") != null) {
            profile.setGender(rs.getString("gender").charAt(0));
        }
        return profile;
    }

    private PreparedStatement createPreparedStatementForSaveProfile(Connection conn, Profile profile) throws SQLException {
        String sql = "INSERT INTO `profiles`.`profile` (`username`, `password`, `nickname`) " +
                "VALUES (?, ?, ?)";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, profile.getUsername());
        ps.setString(2, profile.getPassword());
        ps.setString(3, profile.getNickname());
        return ps;
    }

    private PreparedStatement createPreparedStatementForListProfileByNickname(Connection conn, String nickname) throws SQLException {
        String sql = "SELECT  `profile_id`,  `username`,  `password`,  `nickname`,  `last_online`,  `gender`,  `birthday`,  `location`,  `joined`" +
                "FROM `profiles`.`profile`" +
                "WHERE `nickname`=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, nickname);
        return ps;
    }

    private PreparedStatement createPreparedStatementForGetProfileByUsername(Connection conn, String username) throws SQLException {
        String sql = "SELECT  `profile_id`,  `username`,  `password`,  `nickname`,  `last_online`,  `gender`,  `birthday`,  `location`,  `joined`" +
                "FROM `profiles`.`profile`" +
                "WHERE `username`=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, username);
        return ps;
    }

    private PreparedStatement createPreparedStatementForUpdateProfileById(Connection conn, Profile profile) throws SQLException {
        String sql = "UPDATE `profiles`.`profile`" +
                "SET `nickname`=?,  `gender`=?,  `birthday`=?,  `location`=? " +
                "WHERE  `profile_id`=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, profile.getNickname());
        ps.setString(2, profile.getGender() != null ? String.valueOf(profile.getGender()) : null);
        ps.setTimestamp(3, profile.getBirthday());
        ps.setString(4, profile.getLocation());
        ps.setLong(5, profile.getProfileId());
        return ps;
    }

    private PreparedStatement createPreparedStatementForUpdatePassword(String username, String password) throws SQLException {
        String sql = "UPDATE `profiles`.`profile`" +
                "SET `password`=? " +
                "WHERE  `username`=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, password);
        ps.setString(2, username);
        return ps;
    }
    
    private PreparedStatement createPreparedStatementForUpdateLastOnline(String username) throws SQLException {
        String sql = "UPDATE `profiles`.`profile`" +
                "SET `last_online`=CURRENT_TIMESTAMP " +
                "WHERE  `username`=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, username);
        return ps;
    }
}

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 ⑦ ProfileDAOTest.java

package org.sample.dao;

import org.junit.Test;
import org.sample.dao.impl.ProfileDAOImpl;
import org.sample.db.StaticConnectionFactory;
import org.sample.entity.Profile;

import java.sql.SQLException;
import java.util.List;

import static org.junit.Assert.*;

public class ProfileDAOTest {

    private ProfileDAO profileDAO = new ProfileDAOImpl();

    private static final String USER_NAME = "hello123";

    @Test
    public void saveProfile() {
        Profile profile = new Profile();
        profile.setUsername(USER_NAME);
        profile.setPassword("231231232");
        profile.setNickname("jack");
        int i = profileDAO.saveProfile(profile);
        System.out.println(i);
    }

    @Test
    public void listProfileByNickname() {
        List<Profile> profiles = profileDAO.listProfileByNickname("123");
    }

    @Test
    public void getProfileByUsername() {
        Profile existProfile = profileDAO.getProfileByUsername(USER_NAME);
        Profile notExistProfile = profileDAO.getProfileByUsername(USER_NAME + "321");
        assertNotNull(existProfile);
        assertNull(notExistProfile);
    }

    @Test
    public void updateProfileById() {
        Profile profile = profileDAO.getProfileByUsername(USER_NAME);
        int i = profileDAO.updateProfileById(profile);
        assertEquals(1, i); // 即使沒改變值,可是仍是會從新set一遍,所以影響行數仍是一行

        profile.setGender('f');
        profile.setNickname("www" + Math.random());
        int j = profileDAO.updateProfileById(profile);
        assertEquals(1, j);
    }

    @Test
    public void updatePassword() {
        profileDAO.updatePassword(USER_NAME, "www" + Math.random());
    }

    @Test
    public void updateLastOnline() throws SQLException {
        try {
            StaticConnectionFactory.setAutoCommit(false);
            profileDAO.getProfileByUsername(USER_NAME);
            profileDAO.updateLastOnline(USER_NAME);
            StaticConnectionFactory.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                StaticConnectionFactory.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            StaticConnectionFactory.setAutoCommit(true);
        }
    }
}
相關文章
相關標籤/搜索