JDBC操作数据库

前言

JDBC可以理解为是一套提供给数据库厂商的一套接口规范,而作为程序员的我们在导入了对应厂商提供驱动jar包注册驱动后,可以用相同的方式(接口方法)去调用不同厂商的数据库
我们不同持久层框架底层,如mybatis、hibenate底层的基石

一、获取连接的方式

不同数据库链接方式
jdbc:oracle:thin:@localhost:1521:sid
jdbc:microsoft:sqlserver//localhost:1433;DatabaseName=sid
jdbc:MysqL://localhost:3306/sid

/**
* 获取数据库链接方式1:但是修改时需要代码,所以一般用方式2
*/
public class Demo1 {
    public static void main(String[] args) throws Exception {
        // 注册驱动,因为是必要步骤,在很多高版本内部已经替我们写好了,现在只需要加载即可
        /*Class clazz = Class.forName("com.MysqL.jdbc.Driver");
        Driver driver =(Driver)clazz.newInstance();
        DriverManager.registerDriver(driver);*/
        Class.forName("com.MysqL.jdbc.Driver");
        // 获取连接,分别是数据库地址,用户,密码
        Connection conn =
                DriverManager.getConnection(
                        "jdbc:MysqL://127.0.0.1:13306/test1?useUnicode=true&characterEncoding=utf8",
                        "root",
                        "123456");
        System.out.println(conn);//com.MysqL.jdbc.JDBC4Connection@6fd02e5
    }
}
/**
* 获取数据库链接方式2:将变动位置抽取成配置文件
*/
public class Demo1 {
    public static void main(String[] args) throws Exception {
        InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);
        String url = properties.getProperty("url");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");

        Class.forName(driver);
        // 获取连接
        Connection conn = DriverManager.getConnection(url,username,password);
        System.out.println(conn);
    }
}

/**
* MysqL的Driver类,加载时就会触发静态代码块
*/
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (sqlException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    public Driver() throws sqlException {
        // required for Class.forName().newInstance()
    }
}

二、操作数据库

1、使用Statement的方式 - 这种方法sql注入问题,不要使用

public class Demo1 {
    static String url = "";
    static String username = "";
    static String password = "";
    static {
        try {
            InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);
            String driver = properties.getProperty("driver");
            Class.forName(driver);
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
        } catch (Exception e) {
            e.printstacktrace();
        }
    }
    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection(url,username,password);
        Statement statement = conn.createStatement();
        // 假设这个就是传进来的参数
        String param = " 5 OR 1 = 1";
        String sql = "SELECT * FROM users WHERE uid = " + param;
        // 5.处理结果集
        ResultSet rs = statement.executeQuery(sql);
        while (rs.next()) {
            String uid = rs.getString("uid");
            String name = rs.getString("name");
            String pwd = rs.getString("pwd");
            System.out.println(uid + name + pwd);
        }
        // 资源的关闭
        rs.close();
        statement.close();
        conn.close();
    }
}

2、使用PreparedStatement方式 - 无sql注入问题,推荐使用

修改1中的main方法,使用预编译的方式执行sql,我们把要成为条件的位置,使用?占位,然后手动set设置值

public static void main(String[] args) throws Exception {
	 // 假设这个就是传进来的参数
	 int param = 9;
	 String sql = "SELECT * FROM USERS WHERE UID = ?";
	 Connection conn = DriverManager.getConnection(url,username,password);
	 PreparedStatement preparedStatement = conn.prepareStatement(sql);
	 preparedStatement.setInt(1, param);
	 // 5.处理结果集
	 ResultSet rs = preparedStatement.executeQuery();
	 while (rs.next()) {
	     String uid = rs.getString("uid");
	     String name = rs.getString("name");
	     String pwd = rs.getString("pwd");
	     System.out.println(uid + name + pwd);
	 }
	 // 资源的关闭
	 rs.close();
	 preparedStatement.close();
	 conn.close();
}

三、元数据

我们一般会将实体类数据库表字段进行映射,但是如果按照上面案例的写法,就需要自己创建实体类,然后一个个手动从结果集中取值,实体类set设置值。元数据可以让我们获取数据库表信息,即字段类型,名称,有了它们我们就可以通过反射来映射进我们的实体类中。

public class Demo1 {
    static String url = "";
    static String username = "";
    static String password = "";
    static {
        try {
            InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);
            String driver = properties.getProperty("driver");
            Class.forName(driver);
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
        } catch (Exception e) {
            e.printstacktrace();
        }
    }
    public static void main(String[] args) throws Exception {
        // 假设这个就是传进来的参数
        int param = 9;
        String sql = "SELECT * FROM USERS WHERE UID = ?";
        Connection conn = DriverManager.getConnection(url,username,password);
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setInt(1, param);
        ResultSet rs = preparedStatement.executeQuery();
        System.out.println("============获取元数据信息=============");
        ResultSetMetaData MetaData = rs.getMetaData();
        int columnCount = MetaData.getColumnCount();//列数
        // 遍历结果集
        while (rs.next()) {
        	// 这个位置也可以通过反射来获取类型与实体类
            Users users = new Users();
            // 根据表列数决定循环次数,然后将当条数据每个字段一个个取出来
            for (int i = 0; i < columnCount; i++) {
                Object colValue = rs.getobject(i + 1);
                // 获取列的列明(表中的真实名字)
                //String columnName = MetaData.getColumnName(i + 1);
                // 获取列的别名,没用则用列名,防止表和实体类名称不一致问题
                String columnName = MetaData.getColumnLabel(i + 1);
                try {
                    Field field = Users.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(users, colValue);
                }catch (Exception e){

                }
            }
            System.out.println(users);
        }
        // 资源的关闭
        rs.close();
        preparedStatement.close();
        conn.close();
    }
}
class Users{
    int uid;
    String name;
    String pwd;
    @Override
    public String toString() {
        return "Users{" +
                "uid=" + uid +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

四、开启事务与设置隔离级别

对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种井发问题:。
- 脏读:对于两个事务T1, T2, T1读取了已经被T2更新但还没有被提交的字段。之后,若T2回滚,T1读取的内容就是临时且无效的。
- 不可重复读:对于两个事务T1, T2, T1读取了一个字段,然后T2更新了该字段。之后,T1再次读取同一个字段,值就不同了。
- 幻读:对于两个事务T1, T2, T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行。之后,如果T1再次读取同一个表,就会多出几行。

public static void main(String[] args) throws Exception {
    // 假设这个就是传进来的参数
    int param = 9;
    String sql = "SELECT * FROM USERS WHERE UID = ?";
    Connection conn = DriverManager.getConnection(url,username,password);
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        // 不让连接自动提交
        conn.setAutoCommit(false);
        // 设置隔离级别为可重复读
        conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
        ps = conn.prepareStatement(sql);
        rs = ps.executeQuery();
        // 处理结果集.....
        // ....
        // 提交
        conn.commit();
    }catch (Exception e){
        // 异常了回滚操作
        conn.rollback();
    }finally {
        // 正常每个关闭都需要try catch的,这里减少代码行数没写
        rs.close();
        ps.close();
        conn.close();
    }
}

5、数据库连接池

为什么要使用连接池?我们看这句代码Connection conn = DriverManager.getConnection(url,username,password); conn.close();用于获取数据库连接的,用完后还要关闭连接。这就好比打电话,拨通-聊天-挂断,数据库作为我们每分每秒都在使用的工具,频繁的连接是会浪费时间与消耗性能的,每次连接都要重新校验你的身份信息等。连接池可以简单理解为一个List集合,我们把连接放入到集合中,当线程访问时给它分发一个,用完在还回来,节省了建立连接所带来的性能消耗。
连接池的用法大同小异,因为JDK里也给了标准接口DataSource,常用的连接池有DBCP、C3PO、Druid

public static void main(String[] args) throws Exception {
    InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
    Properties properties = new Properties();
    properties.load(is);
    is.close();
    // 创建数据源
    DataSource createDataSource = DruidDataSourceFactory.createDataSource(properties);
    // 获取连接
    Connection connection = createDataSource.getConnection();
    // 看似是关闭连接,实际上是放回到连接池
    connection.close();
}
/**
* 如果不知道配置文件里有哪些参数,可以点进去看源码,能配置的有很多,下图三个就是用户名、密码、连接地址
*/
public static void config(DruidDataSource dataSource, Map<?, ?> properties) throws sqlException {
    String value = null;
    value = (String) properties.get(PROP_PASSWORD);
    if (value != null) {
        dataSource.setPassword(value);
    }
    value = (String) properties.get(PROP_URL);
    if (value != null) {
        dataSource.setUrl(value);
    }
    value = (String) properties.get(PROP_USERNAME);
    if (value != null) {
        dataSource.setUsername(value);
    }
	// ....还有好多可以配置的
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...