技术干货

Java JDBC 操作MySQL,Properties,MyS

2018-08-12  本文已影响33人  两分与桥

数据库基本操作

数据库
    RDBMS,relation database management system,关系型数据库管理系统。
        存放的是结构化数据
    
SQL:Structured Query Language,结构化查询语言

CRUD
    insert into table_name(field_name,...) values(value,...)
    
    select id,... from table_name where id=xxx,...
    
    update table_name set id=xxx,... where id=xxx
    
    delete from test where ...

JDBC介绍

 * JDBC是Java应用程序和数据库之间的通信桥梁,是Java应用程序访问数据库的通道

 * JDBC代表Java数据库连接(Java Database Connectivity),它是用于Java编程语言和数据库之间的
 * 数据库无关连接的标准Java API,换句话说:JDBC是用于在Java语言编程中与数据库连接的API。
 * 
 * 1.JDBC标准主要由一组接口组成,其好处就是统一了各种数据库访问方式。
 * 2.JDBC接口的实现类称为数据库驱动,有各个数据库厂商提供,使用JDBC必须导入特定的驱动

JDBC与数据库通信就是进程间的通信,用的就是套接字。

对于数据库,我用的是phpstudy自带的MySQL数据库,phpstudy的安装十分简单,一直点击下一步就行,数据库账号密码默认都是root

java JDBC连接数据库的步骤

 * 1.导入JDBC驱动jar
 * 2.注册JDBC驱动
 *      -参数:"驱动程序类名"
 *      -Class.forName("驱动程序类名")
 * 3.获得Connection对象
 *      -需要三个参数:url,username,password
 *      -连接到数据库
 * 4.创建Statement(语句)对象
 *      -conn.getStatement()方法创建对象
 *      -用于执行SQL语句
 *      -execute(ddl) 执行任何SQL,常用执行DDL,DCL
 *      -executeUpdate(dml) 执行DML语句,如:insert,update,delete
 *      -executeQuery(dql) 执行DQL语句,如:select
 * 5.处理SQL执行结果
 *      -execute(ddl) 如果没有异常则执行成功
 *      -executeUpdate 返回数字,表示更新"行"数量,抛出异常则失败
 *      -executeQuery(dql) 返回ResultSet(结果集)对象,代表执行结果
 *          使用for遍历处理,如果查询失败抛出异常!
 * 6.关闭数据连接,
 *      -conn.close();

maven导包,引入MySQL驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>
package com.libai;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class Demo1 {
    public static void main(String[] args) {
        try {
             * 注册驱动程序
            Class.forName("com.mysql.jdbc.Driver");

             * 连接到数据库
             * getConnection()方法查找并且尝试连接到数据库
            String url = "jdbc:mysql://localhost:3306/test";
            String username = "root";
            String password = "root";
            Connection conn = DriverManager.getConnection(url, username, password);

             * 输出conn引用对象的实际类型
             * 证明:驱动程序提供了Connection接口的实现类
            System.out.println(conn.getClass());
            
             * 创建Statement语句对象
            Statement st = conn.createStatement();
            String ddl = "create table rabbit (id INT PRIMARY KEY auto_increment,name varchar(100))";
            String delsql = "drop table rabbit";
            boolean b = st.execute(delsql);

             * 返回结果:true 表示有结果集
             *          false 没有结果集
             *          创建失败抛出异常
             * 如果没有异常,则创建成功!--- 也就是false

            System.out.println(b);
            st.close();
            conn.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印:
class com.mysql.jdbc.JDBC4Connection
false

插入数据,使用executeUpdate()

String sql = "insert into rabbit set id=2,name='frame2'";
int n = st.executeUpdate(sql);
System.out.println(n);

打印:
1   --- 修改了一行,输出正确

select查询数据,使用executeQuery()

String sql = "select id,name from rabbit";
ResultSet n = st.executeQuery(sql);

* 处理结果····
* n.next():移动结果集游标到下一行,默认在第一行之前
* 检查是否有数据,如果有返回true,否则false
while(n.next()) {
    * getXXX(列名):返回结果集当前行指定列名的数据
    System.out.print(n.getInt("id"));
    System.out.print(n.getString("name"));
}

Properties 是Java专门读取配置文件的API

db.properties文件,建立在与main同级的resources下

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root
package com.libai;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

* Properties 其目的就是为了读取 .properties文件,本身实现了Map接口
* 内部是散列表,限定了key和value都是String类型。
* 方法:load(流)将文件读取为散列表
* getProperty(key)查询value

public class Demo5 {
    public static void main(String[] args) {
        try {
            Properties prop = new Properties();
            System.out.println(prop);
            System.out.println(prop.size());
            System.out.println(prop.isEmpty());
            
             * 从包中加载资源
            InputStream input = Demo5.class.getClassLoader().getResourceAsStream("db.properties");

             * 将文件内容读取到散列表中
            prop.load(input);
            
             * 查询数据,读取文件内容
            String driver = prop.getProperty("jdbc.driver");
            System.out.println(driver);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


修改数据参数后重新写入到文件中
Properties prop = new Properties();
prop.load(new FileInputStream(path));
prop.setProperty("name", "libai");
prop.setProperty("age", 22 + "");
prop.store(new FileOutputStream(path), "我的注释");

将Properties与JDBC查询数据库结合起来,方便之后修改数据库连接信息(总不能老是去修改java代码吧)

 * Properties读取配置文件,
 * 
 * Properties是java中专门用于读取配置文件的API
 * 管理数据库连接
 * 在软件中数据库连接使用非常频繁,如果每次都创建连接,就会造成代码的大量冗余,
 * 常规的做法是建立数据库连接工具类,封装数据库连接过程,统一数据库连接过程,使用时候就可以简化代码。
 * 
 * 
 * 实现步骤:
 * 1.创建数据库连接参数文件,db.properties
 * 2.创建DbUtils.java封装数据库连接方法
 *      -利用Properties读取配置文件中的数据库连接参数
 *      -创建方法 getConnection封装数据库连接过程
 * 3.使用getConnection方法

下面是封装了Properties读取数据库配置信息的和连接数据库的类

package com.libai;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class DbUtils {

     * 封装创建数据库连接的过程,简化数据库连接
     * 因为只需要一份,则定义为静态变量
    static String driver;
    static String url;
    static String username;
    static String password;
    static {
        try {
            Properties cfg = new Properties();
            InputStream input = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
            cfg.load(input);
            driver = cfg.getProperty("jdbc.driver");
            url = cfg.getProperty("jdbc.url");
            username = cfg.getProperty("jdbc.username");
            password = cfg.getProperty("jdbc.password");
            System.out.println(cfg);
----上面这句打印{jdbc.password=root, jdbc.username=root, jdbc.url=jdbc:mysql://localhost/test, initSize=5, jdbc.driver=com.mysql.jdbc.Driver, maxActive=10}
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        try {
            Class.forName(driver);
            Connection conn = DriverManager.getConnection(url,username,password);
            return conn;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
     * 关闭数据库的连接
    public static void close(Connection conn) {
        try {
            if(conn != null) {
                conn.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 * ResultSet代表DQL查询结果,是2维结果,其内部维护了一个读取数据的游标,
 * 默认情况,游标在第一行数据之前,当调用next()方法时候,游标会向下移动,
 * 并将返回结果集中是否包含数据,如果包含数据就返回true,
 * 结果集还提供了很好get方法用于获取结果集游标指向当前行数据。

调用上面的类实现连接数据库

package com.libai;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo4 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DbUtils.getConnection();
            Statement st = conn.createStatement();
            String sql = "select * from rabbit";
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                System.out.println(rs.getInt("id")+", "+rs.getString("name"));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            DbUtils.close(conn);
        }
    }
}

打印:
1, libai
2, ttt

数据库连接池DBCP

数据库连接池中存放一定数量的数据库连接,需要使用时从其中取,用完了就放回池中,循环使用(数据库连接的连接和关闭都是十分耗费资源的)

单例模式:singleton

池(集合)化模式:
    使用有限的对象数量服务于大量的客户端请求。
    
Datasource
    1.内部是连接池
    2.java.sql.Datasource, Connection getConnection();
    3.Connection.close()

使用DBCP

 * 使用DBCP
 * 
 * 1.导入连接池jar
 * 2.创建连接池对象
 * 3.设置数据库必须的连接参数
 * 4.设置可选的连接池管理策略参数
 * 5.从连接池中获得活动的数据库连接
 * 6.使用连接范围数据库
 * 7.使用以后关闭数据库连接,这个不是真的关闭连接,而是将使用过的连接归还到连接池。
 * 
 * 为了便捷的使用连接池,经常将连接池封装为一个连接管理工具类。

maven导入包DBCP

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

一个数据库连接池的简单例子

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.BasicDataSource;

public class Demo1 {
    public static void main(String[] args) throws SQLException {
        BasicDataSource ds = new BasicDataSource();
        
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost/test";
        String username = "root";
        String password = "root";

         * 设置必须的参数
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        
         * 设置连接池的管理策略参数
        ds.setInitialSize(2);  --- 一开始就设置池中存放两个连接
        ds.setMaxActive(100);   ---最多可以有100个连接
        
         * 使用连接池中的数据库连接,取不到就堵塞
        Connection conn = ds.getConnection();
        
         * 执行SQL
        Statement st = conn.createStatement();
        String sql = "select * from rabbit";
        ResultSet rs = st.executeQuery(sql);
        while(rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id+", "+name);
        }
        
         * 归还连接到数据库连接池!!
        conn.close();
    }
}

打印:
1, libai
2, talent
3, where
4, HOW

db.properties文件添加两条配置

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root

# parameter for BasicDataSource,initSize是初始化建立1个连接,最大2个连接
initSize=1
maxActive=2

DBUtils.java 连接池模块,直接调用静态方法就行,配置参数依据上面的db.properties文件,下面所有的代码用到的全都是下面这个DBUtils.java 连接池模块。

package day02;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import org.apache.commons.dbcp.BasicDataSource;

 * 连接池版本的数据库连接管理工具类,适合于并发场合
public class DBUtils {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    private static int initSize;
    private static int maxActive;
    
    private static BasicDataSource ds;
    
    static {        
        try {
            Properties cfg = new Properties();
            InputStream input = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
            cfg.load(input);
            driver = cfg.getProperty("jdbc.driver");
            url = cfg.getProperty("jdbc.url");
            username = cfg.getProperty("jdbc.username");
            password = cfg.getProperty("jdbc.password");
            initSize = Integer.parseInt(cfg.getProperty("initSize"));
            maxActive = Integer.parseInt(cfg.getProperty("maxActive"));
            
            ds = new BasicDataSource();
            
             * 初始化连接池
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            ds.setInitialSize(initSize);
            ds.setMaxActive(maxActive);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Connection getConnection() {
        Connection conn = null;
        try {
             * getConnection()从连接池中获取重用的连接,如果连接池满了,
             * 则等待,如果有连接归还,则获取重用的连接
            conn = ds.getConnection();
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
     * 将用过的连接归还到连接池
    public static void close(Connection conn) {
        try {
            if(conn != null) {
                conn.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用上面的静态方法连接池

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo2 {
    public static void main(String[] args) {
        Connection conn = DBUtils.getConnection();
        Statement st;
        try {
            st = conn.createStatement();
            String sql = "select * from rabbit where id>2";
            ResultSet rs= st.executeQuery(sql);
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                System.out.println(id+", "+name);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
    }
}

打印:
3, where
4, HOW

线程测试数据连接池的效果

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

 * 线程并发测试
public class Demo3 {
    public static void main(String[] args) {
        for(int i=0; i<5; i++) {
            Thread t = new DemoThread(5000);
            t.start();
        }
    }
}

class DemoThread extends Thread{
    int wait;

    public DemoThread(int wait) {
        this.wait = wait;
    }

    public void run() {
        Connection conn = null;
        try {
             * getConnection方法在连接池中没有连接可以使用的时候,会堵塞等待
            conn = DBUtils.getConnection();
            Statement st = conn.createStatement();
            String sql = "select 'Helllo' as a from dual";
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                System.out.println(Thread.currentThread().getName()+" "+rs.getString("a"));
            }
            Thread.sleep(wait);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+" 归还连接");
            DBUtils.close(conn);
        }
    }
}

 * 连接池中最大只有两个连接时,第三个线程获取连接时会等待,
 * 等待到有连接归还为止

---- 上面的db.preproties文件中最大连接数设置为2了,所以此处两个两个执行
打印:
Thread-0 Helllo
Thread-3 Helllo
Thread-3 归还连接
Thread-0 归还连接
Thread-1 Helllo
Thread-2 Helllo
Thread-2 归还连接
Thread-1 归还连接
Thread-4 Helllo
Thread-4 归还连接

使用PreparedStatement提高数据库系统性能,重用执行计划

SQL语句翻译成执行计划,才能执行
SQL语句完全一样(大小写和空格都一样)的情况下,会重用执行计划
使用带参数的SQL语句可以使得重用执行计划,提高数据库性能

用法跟上面的Statement差不多

package day02;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class Demo4 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();

             * 带参数的SQL语句
            String sql = "insert into rabbit (id,name) values (?,?)";
            
             * 将SQL语句发送到数据库,创建执行计划,返回值ps就代表执行计划
            PreparedStatement ps= conn.prepareStatement(sql);

             * 替换[执行计划]中的参数,2个参数,按照序号发送参数
            ps.setInt(1, 5);
            ps.setString(2, "zhizhang");

             * 执行执行计划
            int n1 = ps.executeUpdate();
            System.out.println(n1);
            
             * 重用
            ps.setInt(1, 6);
            ps.setString(2, "hello");
            int n2 = ps.executeUpdate();
            System.out.println(n2);
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:  --执行成功,返回修改的行数
1
1


换成update,也是类似的
conn = DBUtils.getConnection();
String sql = "update rabbit set name=? where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "demo change1");
ps.setInt(2, 5);
int n1 = ps.executeUpdate();
System.out.println(n1);

ps.setString(1, "demo change2");
ps.setInt(2, 6);
int n2 = ps.executeUpdate();
System.out.println(n2);

note

 * JDBC
 * 数据库连接池
 * 
 * DriverManager管理数据库连接适合单线程情况,而在多线程并发情况下,为了能够重用数据库连接,
 * 同时控制并发连接总数,避免数据库连接超载,一定要使用数据库连接池。
 * 
 * 连接池原理:
 * 数据库连接池的开源实现非常多,DBCP是常用的连接池之一。

只要用到了SQL语句拼接,要思考是否有SQL注入的风险,PreparedStatement可以避免SQL注入的风险,它将单引号等特殊字符转义。

* PreparedStatement 对象用于执行带参数的预编译执行计划
 * 
 * 关于执行计划:
 * 1.任何SQL执行过程都是先编译“执行计划”,再执行“执行计划”
 * 2.数据库为了优化性能,在SQL相同的时候,会重用执行计划
 *      -执行计划编译较慢
 *      -重用执行计划可以提提高数据库性能
 * 3.数据库只在SQL语句完全一样时候才重用相同的执行计划
 *      -如果SQL语句中有一个字符的更改,会创建不同的执行计划
 *      -SQL中一个空格或者一个大小写的不同也会创建不同的执行计划
 * 
 * PreparedStatement 好处是可以重复使用执行计划,提高DB效率
 * 
 * 使用步骤:
 * 1.将带参数的SQL发送到数据库创建执行计划
 * 2.替换执行计划中的参数
 * 3.执行执行计划,得到结果
 * 
 * PreparedStatement 可以避免SQL注入攻击,它将单引号等特殊字符加上反斜杠转义了

SQL注入例子

package day02;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

public class Demo6 {
    public static void main(String[] args) {
        if(login()) {
            System.out.println("登陆成功");
        }else {
            System.out.println("账户名或密码错误");
        }
    }
    
    
    public static boolean login() {
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String username = scan.nextLine().trim();
        System.out.print("请输入密码:");
        String password = scan.nextLine().trim();
        
        Connection conn = null;
        int back = 0;
        try {
            conn = DBUtils.getConnection();
            String sql1 = "select count(*) as back from rabbit where username=? and password=?";
            
            --- 第一种方式,PreparedStatement
            PreparedStatement ps = conn.prepareStatement(sql1);
            ps.setString(1, username);      
            ps.setString(2, password);
            System.out.println("自动拼接:"+ps);
            // ResultSet rs = ps.executeQuery();   ---未执行
            
            --- 第一种方式,Statement
            Statement st = conn.createStatement();
            String sql2 = "select count(*) as back from rabbit where username='"+ username +"' and password='"+ password +"'";
            System.out.println("手动拼接:"+sql2);
            ResultSet rs = st.executeQuery(sql2);

            rs.next();
            back = rs.getInt("back");
            System.out.println("back = " + back);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
        
        if(back >= 1) {
            return true;
        }else {
            return false;
        }
    }
}

打印;
请输入用户名:aa
请输入密码:123' or '1'='1
自动拼接:com.mysql.jdbc.JDBC42PreparedStatement@3daa422a: select count(*) as back from rabbit where username='aa' and password='123\' or \'1\'=\'1'
手动拼接:select count(*) as back from rabbit where username='aa' and password='123' or '1'='1'
back = 6
登陆成功

ResultSet中存在可滚动的结果集,但从来不用,其性能差

获取的ResultSet结果集中,存在获取的数据库信息的元数据,可用ResultSetMetaData API调用获取

 * 结果集元数据
 * ResultSetMetaData用于描述查询结果的相关性信息,其中包含列名称,列数量,类数据类型等

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Demo1 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "select * from rabbit where id=1";
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            --- 调用元数据,具体的信息都可以从接口中取得,具体可以查看java.sql文档
            System.out.println(rs.getMetaData());

            while(rs.next()) {
                String username = rs.getString("username");
                String password = rs.getString("password");
                System.out.println("username = "+username+" password = "+password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:
com.mysql.jdbc.ResultSetMetaData@654f0d9c - Field level information: 
    com.mysql.jdbc.Field@6a400542[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=id,originalColumnName=id,mysqlType=3(FIELD_TYPE_LONG),flags= AUTO_INCREMENT PRIMARY_KEY, charsetIndex=63, charsetName=US-ASCII]
    com.mysql.jdbc.Field@6580cfdd[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=username,originalColumnName=username,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=8, charsetName=WINDOWS-1252]
    com.mysql.jdbc.Field@7e0b85f9[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=password,originalColumnName=password,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=33, charsetName=UTF-8]
username = libai password = 111

JDBC事务控制

 * JDBC 事务控制,Transaction 一组不可分割的操作。
 * 
 * 数据库提供了事务控制功能,支持ACID特性。
 * JDBC提供了API,方便调用数据库的事务功能。
 * 
 * 相关API:
 *  - Connection.getAutoCommit():获得当前事务的提交方式,默认为true
 *  - Connection.setAutoCommit():设置事务的提交属性,参数是
 *      -true:自动提交; -false:不自动提交(默认自动提交)
 *  - Connection.commit():提交事务
 *  - Connection.rollback():回滚事务

特点:Atomic,原子性,不可分割,整体性。
     Consistent,一致性,数据不被破坏。
     isolate,隔离性,事务之间是独立的,不能被干扰的。
     Durable,持续性,数据被永久保存起来。

模拟转账,不符合条件抛出错误回滚,
注意:MySQL数据库测试表格的数据引擎要设置为InnoDB,默认的MyISAM不支持事务处理。

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo2 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            conn.setAutoCommit(false);
            String sql1 = "update myaccount set balance=balance+? where id=?";
            String sql2 = "select balance from myaccount where id=?";
            PreparedStatement ps = conn.prepareStatement(sql1);
            
            * 一个账户减少 1000
            ps.setInt(1, -1000);
            ps.setInt(2, 1);
            int n1 = ps.executeUpdate();
            System.out.println(n1);
            
            * 一个账户增加 1000
            ps.setInt(1, 1000);
            ps.setInt(2, 2);
            int n2 = ps.executeUpdate();
            System.out.println(n2);
            
            ps.close();
            
            * 检测余额是否大于等于0
            ps = conn.prepareStatement(sql2);
            ps.setInt(1, 1);
            ResultSet rs = ps.executeQuery();
            rs.next();
            int balance = rs.getInt("balance");
            if(balance<0) {
                throw new RuntimeException("余额不足,转账失败");
            }else {
                System.out.println("转账成功");
            }

            * 延时20s期间,在命令行中查看当前修改的表格时,是看不到本次交易结果的
            * 命令行中修改时,会堵塞到此次修改完毕
            System.out.println("延时中");
            Thread.sleep(20000);
            
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null) {
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:
1
1
转账成功
延时中


效果图
mysql事务测试
JDBC批量SQL处理
package day03;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;

public class Demo3 {
    public static void main(String[] args) {
        String sql1 = "CREATE TABLE IF NOT EXISTS `account_01` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        String sql2 = "CREATE TABLE IF NOT EXISTS `account_02` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        String sql3 = "CREATE TABLE IF NOT EXISTS `account_03` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            Statement ps = conn.createStatement();

             * 将SQL语句添加到Statement缓存中
            ps.addBatch(sql1);
            ps.addBatch(sql2);
            ps.addBatch(sql3);

             * 执行一批SQL
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:
[0, 0, 0]      ---执行成功

Statement批量插入数据,注意,SQL语句中的并不是单引号,而是tab键上面的那个键的符号,不使用(`)也是可以的。

 * 批量执行
 * 批处理:发送到数据库作为一个单元执行的一组更新语句
 * 
 * 批处理降低了应用程序和数据库之间的网络调用,相比单个SQL语句的处理,批处理更为有效
 * 
 * API方法:
 * - addBatch(String sql)
 *      - Statement类的方法,可以将多条SQL语句添加到Statement对象的
 *        SQL语句列表中
 * - addBatch()
 *      - PreparedStatement类的方法,可以将多条预编译的SQL语句添加到
 *        PreparedStatement对象的SQL语句列表中
 * - executeBatch()
 *      - 把Statement对象或PreparedStatement对象语句列表中的所有SQL
 *       语句发送给数据库进行处理
 * - clearBatch()
 *      - 清空当前SQL语句列表
 * 
 * 
 * 防止批量过大出现OutOfMemory错误:
 * 如果PreparedStatement对象中的SQL列表包含过多的待处理SQL语句,可能会
 * 产生OutOfMemory错误,分段处理缓冲列表
package day03;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;

public class Demo3 {
    public static void main(String[] args) {
        String sql1 = "insert into myaccount (`id`,`balance`) values (1,1000)";
        String sql2 = "insert into myaccount (`id`,`balance`) values (2,2000)";
        String sql3 = "insert into myaccount (`id`,`balance`) values (3,3000)";
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            Statement ps = conn.createStatement();

             * 将SQL语句添加到Statement缓存中
            ps.addBatch(sql1);
            ps.addBatch(sql2);
            ps.addBatch(sql3);

             * 执行一批SQL
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:
[1, 1, 1]     ---执行成功

PreparedStatement批量插入数据

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Arrays;

public class Demo4 {
    public static void main(String[] args) {
        String sql = "insert into myaccount (id,balance) values(?,?)";
        Connection conn = DBUtils.getConnection();
        try {
            PreparedStatement ps = conn.prepareStatement(sql);
            for(int i=0; i<100; i++) {

                 * 替换参数
                ps.setInt(1, i+100);
                ps.setInt(2, i*1000);

                 * 将参数添加到ps缓冲区
                ps.addBatch();

                 * 清空缓冲区,一般是在数据量特别大的时候避免内存溢出
                if(i%8 == 0) {
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
            
        } catch (Exception e) {
            e.printStackTrace();    
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:
[1, 1, 1]   --- 每8个一批已经被执行过了

返回自动主键

JDBC API提供了返回插入数据期间自动生成ID的API
PreparedStatement ps = conn.prepareStatement(sql,列名列表);
ResutSet rs = ps.getGeneratedKeys();

向数据库中插入/读取image和长文本

package test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import org.junit.Test;
import Utils.JDBCUtils;

public class TestCase2 {
     * 向数据库中插入image,长文本
     * 在数据库中image对应的存储类型是longblob,长文本对应的存储类型是longtext
    @Test
    public void test1() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "INSERT INTO customers(name,sex,age,photo,memo) VALUES(?,?,?,?,?)";
        PreparedStatement ppst = conn.prepareStatement(sql);
        ppst.setString(1, "hello");
        ppst.setInt(2, 0);
        ppst.setInt(3, 23);
        ppst.setBinaryStream(4, new FileInputStream("E://TestCase//day20//demo.jpg"));
        ppst.setString(5, "aaaaaaaaaaaaaaaaaaaaa");
        
        ppst.executeUpdate();
        
        ppst.close();
        conn.close();
        System.out.println("over");
    }
    
     * 从数据库中读取image和长文本
    @Test
    public void test2() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "SELECT * FROM customers WHERE id=?";
        PreparedStatement ppst = conn.prepareStatement(sql);
        ppst.setInt(1, 1);
        
        ResultSet rs = ppst.executeQuery();
        rs.next();
        int id = rs.getInt("id");
        String name = rs.getString("name");
        int sex = rs.getInt("sex");
        int age = rs.getInt("age");
        InputStream photo = rs.getBinaryStream("photo");
        String memo = rs.getString("memo");
        
        System.out.println(id + ", " + name + ", " + age + ", " + sex + "," + memo);
        System.out.println("photo length = " + photo.available());
        FileOutputStream fos = new FileOutputStream("E://TestCase//day24//demo.jpg");
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = photo.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        fos.close();
        
        ppst.close();
        conn.close();
        System.out.println("over");
    }
}

存储过程

是在服务器中直接调用的,只需要调用时传递必要的参数,就可以直接执行,速度非常快。

前提是数据库不再需要其他的参数来执行数据库的操作了

在Java中用CallableStatement来负责调用数据库的存储过程

存储过程
    store procedure,存放在数据库中的一组SQL,在服务器端执行
    创建存储过程
        mysql>delimiter //  --声明结束符
    
    create procedure sp_biginsert(in num int)
    begin
        start transaction;
        declare i int default 0;
        while i < num do
            insert into customers(name,age,sex) values(concat('tom',i),i,i);
            set i = i + 1;
        end while;
        commit;
    end//
    
    mysql> show procedure status;//可以查看所有的存储过程
创建存储过程,作用是返回customers表的数据量
    mysql> create procedure sp_count(out param int)
        -> begin
        -> select count(*) into param from customers;
        -> end
        -> //
        Query OK, 0 rows affected (0.11 sec)

调用存储过程
    @Test
    public void test3() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //输出的
        String sql = "{call sp_count(?)}";
        //创建cst对象
        CallableStatement cst = conn.prepareCall(sql);
        //注册输出参数
        cst.registerOutParameter(1, Types.INTEGER);
        //执行存储过程
        cst.execute();
        //取得输出参数
        int count = cst.getInt(1);
        System.out.println(count);
    }
打印结果:取得该表的数据量
10000

返回a+b的值

返回两个数的合
    mysql> create procedure sp_add(in a int, in b int,out c int)
        -> begin
        -> set c:=a+b;
        -> end
        -> //
        Query OK, 0 rows affected (0.00 sec)
调用该存储过程
    @Test
    public void test4() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //输出的
        String sql = "{call sp_add(?,?,?)}";
        //创建cst对象
        CallableStatement cst = conn.prepareCall(sql);
        //对于输入参数需要绑定参数值
        cst.setInt(1, 1);
        cst.setInt(2, 5);
        //注册输出参数
        cst.registerOutParameter(3, Types.INTEGER);
        //执行存储过程
        cst.execute();
        //取得输出参数
        int count = cst.getInt(3);
        System.out.println(count);
    }
打印结果:
5

返回a-b的值,一个数可以作为输入量,也可以作为输出量

返回a-b的值,一个数可以作为输入量,也可以作为输出量
    mysql> create procedure sp_subtract(in a int, inout b int)
        -> begin
        -> set b:=a-b;
        -> end
        -> //
        Query OK, 0 rows affected (0.00 sec)

调用该存储过程
    @Test
    public void test5() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //输出的
        String sql = "{call sp_subtract(?,?)}";
        //创建cst对象
        CallableStatement cst = conn.prepareCall(sql);
        //对于输入参数需要绑定参数值
        cst.setInt(1, 1);
        cst.setInt(2, 5);
        //注册输出参数
        cst.registerOutParameter(2, Types.INTEGER);
        //执行存储过程
        cst.execute();
        //取得输出参数
        int count = cst.getInt(2);
        System.out.println(count);
    }
打印结果:
-4

创建一个函数,并在JDBC中调用

该函数返回a+b的值
    mysql> create function f_add(a int, b int) returns int
        -> return a+b//
    Query OK, 0 rows affected (0.00 sec)

调用该函数
    @Test
    public void test6() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //输出的
        String sql = "{? = call f_add(?,?)}";
        //创建cst对象
        CallableStatement cst = conn.prepareCall(sql);
        //对于输入参数需要绑定参数值
        cst.setInt(2, 100);
        cst.setInt(3, 600);
        //注册输出参数
        cst.registerOutParameter(1, Types.INTEGER);
        //执行存储过程
        cst.execute();
        //取得输出参数
        int count = cst.getInt(1);
        System.out.println(count);
    }
打印结果:
700
上一篇下一篇

猜你喜欢

热点阅读