68. JDBC操作MySQL数据库

对于熟悉PHP的人来说,最熟悉的莫过于PDO(PHP Data Object)这个词了。而且Web后端更多的时候也确实是存取数据,反而对代码性能和封装没太大的讲究。虽然偶尔会有人问我,你觉得一个人,代码写的漂亮,特别好;另一个人代码写的一般,你更倾向于哪一种?我说,如果非要选一种,那肯定是写的漂亮又好的还稳定的。但事实上,并不是这么回事。对很多人来说,二者并不是统一的。

有段时间我还学过Python,但是对于Python操作MySQL数据库相当的泄气,以至于后来我就放弃这门语言了。说实话,这门语言比PHP还有实用的多,而且也是一种新潮的趋势,越来越多的人开始使用这门语言。而且这门语言本身的能力也比PHP要强大的多,可以做很多PHP做不了的事情。

这几天在疯狂的苦学Java,由于之前学过一段时间的Go,觉得差别不是很大,相对来说还是挺容易的。终于学到操作数据库这一节,这应该是对于使用动态语言的人来说,最为期待的一幕了。在动态语言的世界里,数据库相关的操作,占据了业务的大多数,也占据了代码量相当的一部分。觉得静态语言会不会也很难,当然Go使用MySQL数据库是相当的简单,但是Go的DB包不是特别的好用,不过已经非常完美了。

让我感到惊讶的是,Java操作MySQL也是如此的简单,得益于jar包的便利,加入一个扩展是如此的方便和快捷。

一、用户基类

package test;

/**
 * 用户信息
 * 
 * @author zhgxun
 *
 */
public class User {
    public long id;
    public String name;
    public int age;

    public User() {

    }

    public User(long id, String name, int age) {
        this.id = id;
        this.name = name.trim();
        this.age = age;
    }

    public User(String name, int age) {
        this.name = name.trim();
        this.age = age;
    }

    @Override
    public String toString() {
        return "<String: id= " + this.id + ", name= " + this.name + ", age= " + this.age + ">";
    }
}

二、基本查询

package test;

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

/**
 * 使用JDBC连接MySQL数据库
 * 
 * @author zhgxun
 *
 */
public class DB {

    static final String JDBC_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC";
    static final String JDBC_USER = "root";
    static final String JDBC_PASSWORD = "";

    public static void main(String[] args) {
        try {
            // 获取所有用户信息
            List<User> users = new ArrayList<>();
            users = getAllUsers();
            for (User user : users) {
                System.out.println(user);
            }

            // 更新数据记录
            User u = users.get(0);
            u.name = "张十三";
            update(u);

            // 删除跟更新数据类似

            // 新增数据
            long id = insert(new User("关晓彤", 50));
            System.out.printf("新增数据主键:%d\n", id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取用户信息, 注意try()和try...catch() {}的语法格式
     * 
     * @return
     * @throws SQLException
     */
    private static List<User> getAllUsers() throws SQLException {
        try (Connection conn = getConnection()) {
            // 总是优先使用PreparedStatement
            try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM user")) {
                // 查询结果是ResultSet
                try (ResultSet rs = ps.executeQuery()) {
                    List<User> list = new ArrayList<>();
                    while (rs.next()) {
                        long id = rs.getLong("id");
                        String name = rs.getString("name");
                        int age = rs.getInt("age");
                        User std = new User(id, name, age);
                        list.add(std);
                    }
                    return list;
                }
            }
        }
    }

    /**
     * 更新记录
     * 
     * @param u
     * @throws SQLException
     */
    private static void update(User u) throws SQLException {
        try (Connection conn = getConnection()) {
            // 使用PreparedStatement的executeUpdate()进行更新
            try (PreparedStatement p = conn.prepareStatement("UPDATE user SET name=? WHERE id = ?")) {
                // 占位符从1开始
                p.setObject(1, u.name);
                p.setObject(2, u.id);
                // 更新操作包括UPDATE、INSERT和DELETE语句
                // 更新结果是int
                int n = p.executeUpdate();
                System.out.println("Update rows: " + n);
            }
        }
    }

    /**
     * 新增数据
     * 
     * @param u
     * @return
     * @throws SQLException
     */
    private static long insert(User u) throws SQLException {
        try (Connection conn = getConnection()) {
            try (PreparedStatement ps = conn.prepareStatement("INSERT INTO user (name, age) VALUES (?, ?)",
                    Statement.RETURN_GENERATED_KEYS)) {
                ps.setObject(1, u.name);
                ps.setObject(2, u.age);
                int n = ps.executeUpdate();
                System.out.println(n + " inserted.");
                try (ResultSet rs = ps.getGeneratedKeys()) {
                    if (rs.next()) {
                        long id = rs.getLong(1);
                        u.id = id;
                        return id;
                    }
                }
            }
        }
        return 0;
    }

    /**
     * 连接数据库
     * 
     * @return
     * @throws SQLException
     */
    private static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
    }
}

输出

<String: id= 1, name= 张三, age= 18>
<String: id= 2, name= lisi, age= 20>
<String: id= 3, name= wangwu, age= 29>
<String: id= 4, name= maliu, age= 45>
<String: id= 5, name= 田七, age= 30>
<String: id= 6, name= 马云, age= 50>
Update rows: 1
1 inserted.
新增数据主键:7

三、事务处理

package test;

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

/**
 * 事务处理
 * 
 * @author zhgxun
 *
 */
public class DBTx {
    static final String JDBC_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC";
    static final String JDBC_USER = "root";
    static final String JDBC_PASSWORD = "";

    public static void main(String[] args) {
        // 打印用户列表
        List<User> users = null;
        try {
            users = getUsers();
            for (User user : users) {
                System.out.println(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        // 开启更新事务
        System.out.println("开启更新事务");
        Connection conn = null;
        try {
            // 连接数据库
            conn = getConnection();
            // 设置事务隔离级别为提交读
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            // 开启事务, 开启事务即是关闭自动提交即可
            conn.setAutoCommit(false);
            // 更新用户信息
            update(conn, new User(users.get(0).id, "杨幂", 25));
            update(conn, new User(users.get(1).id, "李思思", 20));
            // 提交事务
            conn.commit();
            // 事务执行成功
            System.out.println("事务执行成功");
        } catch (SQLException e) {
            // 回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                System.out.println("回滚事务失败");
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 尝试关闭数据库连接
            try {
                // 恢复数据库自动提交记录
                conn.setAutoCommit(true);
                conn.close();
            } catch (SQLException e) {
                System.out.println("数据库关闭失败");
                e.printStackTrace();
            }
        }

        // 事务后重新打印所有用户列表
        System.out.println("事务后重新打印所有用户列表");
        try {
            users = getUsers();
            for (User user : users) {
                System.out.println(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        // 模拟事务回滚
        System.out.println("模拟事务回滚");
        try {
            // 连接数据库
            conn = getConnection();
            // 设置事务隔离级别为提交读
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            // 开启事务, 开启事务即是关闭自动提交即可
            conn.setAutoCommit(false);
            // 更新用户信息
            update(conn, new User(users.get(0).id, "周迅", 28));
            update(conn, new User(users.get(1).id, "卓依婷", 24));
            // 提交事务
            conn.commit();
            // 人为制造异常终止事务使其回滚, 不过后续代码也无法正常执行了
            throw new RuntimeException("强制数据库回滚");
        } catch (SQLException e) {
            // 回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                System.out.println("回滚事务失败");
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 尝试关闭数据库连接
            try {
                // 恢复数据库自动提交记录
                conn.setAutoCommit(true);
                conn.close();
            } catch (SQLException e) {
                System.out.println("数据库关闭失败");
                e.printStackTrace();
            }
        }

        // 回滚事务后再次查看用户列表
        System.out.println("回滚事务后再次查看用户列表");
        try {
            users = getUsers();
            for (User user : users) {
                System.out.println(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 数据库连接
     * 
     * @return
     * @throws SQLException
     */
    private static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
    }

    /**
     * 获取用户列表
     * 
     * @return
     * @throws SQLException
     */
    private static List<User> getUsers() throws SQLException {
        try (Connection conn = getConnection()) {
            try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM user")) {
                try (ResultSet rs = ps.executeQuery()) {
                    List<User> users = new ArrayList<>();
                    while (rs.next()) {
                        long id = rs.getLong("id");
                        String name = rs.getString("name");
                        int age = rs.getInt("age");
                        users.add(new User(id, name, age));
                    }
                    return users;
                }
            }
        }
    }

    /**
     * 根据id更新用户姓名
     * 
     * @param conn
     * @param user
     * @throws SQLException
     */
    private static void update(Connection conn, User user) throws SQLException {
        try (PreparedStatement ps = conn.prepareStatement("UPDATE user SET name = ? WHERE id = ?")) {
            ps.setObject(1, user.name);
            ps.setObject(2, user.id);
            ps.executeUpdate();
        }
    }

}

输出

<String: id= 1, name= 周迅, age= 18>
<String: id= 2, name= 卓依婷, age= 20>
<String: id= 3, name= wangwu, age= 29>
<String: id= 4, name= maliu, age= 45>
<String: id= 5, name= 田七, age= 30>
<String: id= 6, name= 马云, age= 50>
<String: id= 7, name= 关晓彤, age= 50>
开启更新事务
事务执行成功
事务后重新打印所有用户列表
<String: id= 1, name= 杨幂, age= 18>
<String: id= 2, name= 李思思, age= 20>
<String: id= 3, name= wangwu, age= 29>
<String: id= 4, name= maliu, age= 45>
<String: id= 5, name= 田七, age= 30>
<String: id= 6, name= 马云, age= 50>
<String: id= 7, name= 关晓彤, age= 50>
模拟事务回滚
Exception in thread "main" java.lang.RuntimeException: 强制数据库回滚
    at test.DBTx.main(DBTx.java:98)

四、连接池

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

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

/**
 * 数据库连接池
 */
public class DBSource {
    // 配置MySQL连接属性
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC";
    private static final String JDBC_USER = "root";
    private static final String JDBC_PASSWORD = "";

    /**
     * 使用连接池查询数据
     *
     * @param args 输入参数
     */
    public static void main(String[] args) {
        // 创建连接池
        DataSource dataSource = create();
        // 保持线程实例
        List<Thread> threads = new ArrayList<>();
        // 启动4个线程来查询用户列表
        for (int i = 1; i <= 4; i++) {
            // 准备线程池
            Thread thread = new Thread() {
                public void run() {
                    // 暂缓一段时间
                    try {
                        Thread.sleep((long) (Math.random() * 1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        List<User> users = getUsers(dataSource);
                        for (User user : users) {
                            System.out.println(user);
                        }
                    } catch (SQLException e) {
                        e.getStackTrace();
                    }
                }
            };
            threads.add(thread);
        }
        // 启动线程
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.getStackTrace();
            }
        }
        // 关闭连接池
        ((HikariDataSource) dataSource).close();
    }

    /**
     * 配置数据库连接池
     *
     * @return DataSource
     */
    private static DataSource create() {
        // HikariCP是JDBC连接池组件
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(JDBC_URL);
        config.setUsername(JDBC_USER);
        config.setPassword(JDBC_PASSWORD);
        // 连接超时
        config.addDataSourceProperty("connectionTimeout", 1000);
        // 空闲超时
        config.addDataSourceProperty("idleTimeout", 1000);
        // 最大连接数
        config.addDataSourceProperty("maximumPoolSize", 10);

        return new HikariDataSource(config);
    }

    /**
     * 通过连接池获取用户列表
     *
     * @param dataSource 数据库连接池
     * @return List<User>
     * @throws SQLException 异常处理
     */
    private static List<User> getUsers(DataSource dataSource) throws SQLException {
        try (Connection conn = dataSource.getConnection()) {
            System.out.println("Using connection: " + conn);
            try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM user")) {
                try (ResultSet rs = ps.executeQuery()) {
                    return handleUsers(rs);
                }
            }
        }
    }

    /**
     * 获取用户列表
     *
     * @param rs ResultSet 结果集
     * @return List<User>
     * @throws SQLException 异常处理
     */
    private static List<User> handleUsers(ResultSet rs) throws SQLException {
        List<User> users = new ArrayList<>();
        while (rs.next()) {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            users.add(new User(id, name, age));
        }
        return users;
    }
}

输出

Using connection: HikariProxyConnection@419263385 wrapping com.mysql.cj.jdbc.ConnectionImpl@a0deed5
Using connection: HikariProxyConnection@1809139274 wrapping com.mysql.cj.jdbc.ConnectionImpl@6512b540
<User: id = 1, name= 周迅, age = 18>
<User: id = 2, name= 卓依婷, age = 20>
<User: id = 3, name= wangwu, age = 29>
<User: id = 4, name= maliu, age = 45>
<User: id = 5, name= 田七, age = 30>
<User: id = 6, name= 马云, age = 50>
<User: id = 7, name= 关晓彤, age = 50>
<User: id = 1, name= 周迅, age = 18>
<User: id = 2, name= 卓依婷, age = 20>
<User: id = 3, name= wangwu, age = 29>
<User: id = 4, name= maliu, age = 45>
<User: id = 5, name= 田七, age = 30>
<User: id = 6, name= 马云, age = 50>
<User: id = 7, name= 关晓彤, age = 50>
Using connection: HikariProxyConnection@1352891292 wrapping com.mysql.cj.jdbc.ConnectionImpl@a0deed5
<User: id = 1, name= 周迅, age = 18>
<User: id = 2, name= 卓依婷, age = 20>
<User: id = 3, name= wangwu, age = 29>
<User: id = 4, name= maliu, age = 45>
<User: id = 5, name= 田七, age = 30>
<User: id = 6, name= 马云, age = 50>
<User: id = 7, name= 关晓彤, age = 50>
Using connection: HikariProxyConnection@1970931927 wrapping com.mysql.cj.jdbc.ConnectionImpl@a0deed5
<User: id = 1, name= 周迅, age = 18>
<User: id = 2, name= 卓依婷, age = 20>
<User: id = 3, name= wangwu, age = 29>
<User: id = 4, name= maliu, age = 45>
<User: id = 5, name= 田七, age = 30>
<User: id = 6, name= 马云, age = 50>
<User: id = 7, name= 关晓彤, age = 50>