JavaEE-JDBC基础

一.JDBC和数据库驱动

JDBC(Java Database connectivity): 由java提供,用于访问数据库的统一API接口规范.
数据库驱动: 由各个数据库厂商提供,用于访问数据库的jar包(JDBC的具体实现),遵循JDBC接口,以便java程序员使用!

二.JDBC快速入门

JDBC接口类
	DriverManager 注册驱动,获取数据库连接Connection
	Connection    数据库连接,获取传输器Statement
	Statement     传输器执行sql语句
	ResultSet	  查询结果集合

1.注册数据库驱动

首先,导入数据库驱动jar包(以MySQL为例,mysql-connector-java-5.1.40-bin.jar),然后注册驱动

// 方法一.导致MySql驱动被注册两次,还导致程序和具体驱动类绑定,切换数据库,需要修改java源码重新编译,不建议使用!
DriverManager.registerDriver(new Driver());

// 方法二.驱动类的静态代码块已经注册驱动,只需要加载驱动类即可
// 用反射加载,与驱动类字符串绑定,字符串可放在配置文件,切换数据库,无需改源码重新编译,只需要修改配置文件!	
Class.forName("com.mysql.jdbc.Driver");

2.建立数据库连接

Connection connection = DriverManager.getConnection(url,user,password);	
	
url格式
	MySql:      jdbc:mysql://ip:3306/sid  (本机地址简写jdbc:mysql:///sid)
	Oracle:     jdbc:oracle:thin:@ip:1521:sid
	SqlServer:  jdbc:microsoft:sqlserver://ip:1433;DatabaseName=sid
	
参数(可选,user和password可以写在url参数中)
	?user=lioil&password=***&useUnicode=true&characterEncoding=UTF-8
	
协议:子协议://ip地址:端口号/库名?参数1=值&参数2=值
jdbc:mysql://localhost:3306/sid?useUnicode=true&characterEncoding=utf-8

3.创建Statement执行SQL

1.Statement常用方法:
	单个执行SQL语句
	boolean execute(String sql)         执行SQL语句,没有结果集返回false,有返回true(通过Statement.getResultSet获取结果集);
	ResultSet executeQuery(String sql)  执行select语句,返回结果集
	int executeUpdate(String sql)       执行insert delete update语句,返回影响行数
	
	批量执行SQL语句(insert update delete)
	void addBatch(String sql)           批量添加SQL语句(insert update delete)
	int[] executeBatch()                批量传输SQL到数据库执行,返回每个SQL语句影响行数(数组)
	void clearBatch()                   清空SQL

3.单个执行SQL语句
	// 方法一.Statement
	Statement statement = connection.createStatement();
	int row = statement.executeUpdate(sql);
	ResultSet resultSet = statement.executeQuery(sql);
	
	ResultSet默认不能反向修改数据库记录,但可通过指定参数Statement来创建可改数据的ResultSet,不建议使用,应该用update语句修改数据!!!		
		Statement state = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE);		
		ResultSet resultSet = state.executeQuery("select * from user");		
		resultSet.next();		
		resultSet.updateString("name", "lioil");		
		resultSet.updateRow();
		
		Statement createStatement(int resultSetType, int resultSetConcurrency)  
		resultSetType 结果集类型   
			ResultSet.TYPE_FORWARD_ONLY        不支持滚动,只能向前.
			ResultSet.TYPE_SCROLL_INSENSITIVE  支持滚动,迟钝不敏感
			ResultSet.TYPE_SCROLL_SENSITIVE    支持滚动,敏感			
		resultSetConcurrency 是否支持修改类型
			ResultSet.CONCUR_READ_ONLY 	 不支持修改
			ResultSet.CONCUR_UPDATABLE   支持修改

	// 方法二.PrepareStatement预编译防止SQL注入攻击,用户参数?只做参数,不参与编译,传入SQL关键字无用
	PrepareStatement statement = connection.prepareStatement("SELECT * FROM user WHERE name=? AND password=?");
	statement.setString(1, "lioil");
	statement.setString(2, "12345");	
	ResultSet resultSet = statement.executeQuery();
	
	statement.setInt(...);
	statement.setDouble(...);
	statement.setDate(...);
	
	SQL注入:用户恶意传入一些SQL特殊关键字,导致SQL语义变化
	SELECT * FROM user WHERE name='lioil' AND password='12345';          -- 正常	
	SELECT * FROM user WHERE name='lioil' OR 1=1; --' AND password='1';  -- 恶意注入'lioil' OR 1=1; --' 使语句在OR 1=1;处结束,password没有执行

4.批量执行SQL
Statement批处理		
	Statement smt = conn.createStatement();
	smt.addBatch(sql1);
	smt.addBatch(sql2);
	smt.addBatch(sql3);	
	smt.executeBatch();
	
	优点:可执行多条不同结构SQL语句
	缺点:没预编译,效率低
	
PrparedStatement批处理		
	PrepareStatement preSmt = conn.prepareStatement("insert into user values(null,?)");			
	for(int i=1;i<=100000;i++){
		preSmt.setString(1,"id_"+i);
		preSmt.addBatch();		
		// 1000条为一批次, 减少内存占用
		if(i%1000==0){
			preSmt.executeBatch();
			preSmt.clearBatch();
		}
	}
	preSmt.executeBatch();
	
	优点:SQL结构相同,只编译一次,效率高
	缺点:只能执行相同结构SQL语句

4.结果集ResultSet(在内存以表结构形式存在)

1.移动结果集的指针/游标/当前行:
	boolean next()            移到下一行, 这行有数据返回true, 无返回false
	boolean Previous()        移到前一行
	boolean absolute(int row) 移到指定行	
	boolean first()           移到首行
	boolean last()            移到尾行
	void beforeFirst()        移到首行前一行
	void afterLast()          移到尾行后一行
	
2.获取数据
	String getString(int cloumnCount)   根据列索引,从当前行中获得String 类型数据
	String getString(String columnName) 根据列名,从当前行中获得String类型数据
	String getInt(...)
	String getLong(...)
	String getFloat/getDouble(...)
	String getDate(...)
	
实例:
	while(resultSet.next()){
		String val2 = resultSet.getString(2);
		Float val3 = resultSet.getFloat(3);
		Date val4 = resultSet.getDate(4);
		...
	}

5.释放资源

数据库的connection个数有限,用完立即释放; 而statement和resultSet占用内存,也要释放;
释放顺序: 后创建的,先释放!
try{
	//JDBC代码...
}catch (Exception e) {
	e.printStackTrace();
}finally{
	if(resultSet != null){
		try{
			resultSet.close();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			resultSet = null;
		}
	}
	if(statement != null){
		try {
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			statement = null;
		}
	}
	if(connection != null){
		try {
			connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			connection = null;
		}
	}
}

三.大文本和大二进制文件(不常用)

1.大文本
	Text(Clob): TINYTEXT(255), TEXT(64k), MEDIUMTEXT(16M), LONGTEXT(4G)
	
	//建表
	create table txtTable(
		name CHAR(10),
		txt LONGTEXT
	)
		
	//插入
	File txtFile = new File("lioil.txt");
	PrepareStatement preSmt = conn.prepareStatement("insert into txtTable values(?,?)");
	preSmt.setString(1, "文本");		
	preSmt.setCharacterStream(2, new FileReader(txtFile), (int) txtFile.length());
		
	//查询
	Reader rd = resultSet.getCharacterStream("txt");
		
2.大二进制(多媒体文件)
	Blob: TINYBLOB(255), BLOB(64k), MEDIUMBLOB(16M), LONGBLOB(4G)
	
	//建表
	create table mp4Table(
		name CHAR(10),
		mp4 LONGBLOB
	)
		
	//插入
	File mp4File = new File("lioil.mp4");
	PrepareStatement preSmt = conn.prepareStatement("insert into mp4Table values(?,?)");
	preSmt.setString(1, "视频");		
	preSmt.setBinaryStream(2, new FileInputStream(mp4File), (int) mp4File.length());
		
	//查询
	InputStream in = resultSet.getBinaryStream("mp4");

简书: http://www.jianshu.com/p/ed1a59750127
CSDN博客: http://blog.csdn.net/qq_32115439/article/details/54746135
GitHub博客:http://lioil.win/2017/01/26/JDBC.html
Coding博客:http://c.lioil.win/2017/01/26/JDBC.html