08第十天JDBC开发
一、JDBC:Java
DataBase Connectivity
(一)、JDBC基础
1、数据库驱动:
数据库厂商为了方便开发人员从程序中操作数据库而提供的一套jar包,通过导入这个jar包就可以调用其中的方法操作数据库,这样的jar包就叫做数据库驱动。
2、JDBC:两个包,包括java.sql和javax.sql。开发JDBC应用需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。
sun定义的一套标准,本质上是一大堆的操作数据库的接口,所有数据库厂商为java设计的数据库驱动都实现过这套接口,这样一来同一了不同数据库驱动的方法,开发人员只需要学习JDBC就会使用任意数据库驱动了。
(二)、JDBC实现
1、新建Java项目-->新建lib文件夹-->导入mysql-connector-java-5.1.40-bin.jar-->邮右键build path -->add to build path--》新建类。
六个步骤实现JDBC:
package com.lmd.jdbc;
//接口,面向接口编程,更换数据库不变,通用性
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//mysql做的实现
import com.mysql.jdbc.Driver;
public class JDBCDemo1 {
public static void main(String[] args) throws SQLException{
//1.注册数据库驱动
DriverManager.registerDriver(new Driver());
//Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
String url = "jdbc:mysql://localhost:3306/day10";
Connection con = DriverManager.getConnection(url, "root", "666666");
//Connection con = DriverManager.getConnection(url +
//"?user=root&password=666666");
//Connection con = DriverManager.getConnection("jdbc:mysql:///
//day10?user=root&password=666666");
//3.获取传输器对象
Statement st = con.createStatement();
//4.利用传输器传输sql语句到数据库中执行,获取结果集对象
ResultSet rs = st.executeQuery("select * from user");
//5.遍历结果集获取查询结果
while(rs.next()){
String name = rs.getString("name");
System.out.println(name);
}
//6.关闭资源
rs.close();
st.close();
con.close();
}
}
2、DriverManager
(1)、Jdbc程序中的DriverManager用于加载驱动,并创建与数据库的链接,这个API的常用方法:
DriverManager.registerDriver(new Driver());
DriverManager.getConnection(url, user, password);
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1)、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2)、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。 创建mysql的Driver对象,导致程序和具体的mysql驱动绑死在一起,在切换数据库时需改动JAVA代码。
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// Register ourselves with the DriverManager
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()
}
}
(2)、推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。
3、数据库URL
URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
常用数据库URL地址的写法:
Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer—jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql—jdbc:mysql://localhost:3306/sid
Mysql的url地址的简写形式: jdbc:mysql:///sid
常用属性:useUnicode=true&characterEncoding=UTF-8
4、程序详解—Connection
Jdbc程序中的Connection,它用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
ResultSet createStatement():创建向数据库发送sql的statement对象。
PrepareStatement prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
CallableStatement prepareCall(sql):创建执行存储过程的callableStatement对象。
void setAutoCommit(boolean autoCommit):设置事务是否自动提交。
void commit() :在链接上提交事务。
void rollback() :在此链接上回滚事务。
5、程序详解—Statement
Jdbc程序中的Connection,它用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
ResultSet executeQuery(String sql) :用于向数据发送查询语句。int executeUpdate(String sql):用于向数据库发送insert、update或delete语句
boolean execute(String sql):用于向数据库发送任意sql语句
void addBatch(String sql) :把多条sql语句放到一个批处理中。
int[] executeBatch():向数据库发送一批sql语句执行。
6、程序详解—ResultSet
(1)、 Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
(2)、ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
获取任意类型的数据
getObject(int index)
getObject(string columnName)
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
ResultSet还提供了对结果集进行滚动的方法:
boolean next():移动到下一行
boolean previous():移动到前一行
boolean absolute(int row):移动到指定行
void beforeFirst():移动resultSet的最前面。
void afterLast() :移动到resultSet的最后面。
例如:
表中:zs lisi wangwu
rs.next(); rs.next(); 打印:lisi
rs.previous();//zs
rs.absolute(3);//wangwu 第三行
String name = rs.getString("name");
System.out.println(name);
7、程序详解—释放资源
(1)、Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,
这些对象通常是ResultSet、Statement和Connection对象。
(2)、特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,
极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
(3)、为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
若 if (rs != null) rs.close(); 加到 try...catch...中,后面要加一个 rs=null; 变成垃圾对象被回收。完整的如下:
public class JDBCDemo1 {
public static void main(String[] args){
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/day10";
conn = DriverManager.getConnection(url, "root", "666666");
stat = conn.createStatement();
rs = stat.executeQuery("select * from user");
while(rs.next()){
String name = rs.getString("name");
System.out.println(name);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
conn = null;
}
}
}
}
}
(三)、JDBC增删改查
create database day10 character set utf8 collate utf8_general_ci;
use day10;
create table user(
id int primary key auto_increment,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
)character set utf8 collate utf8_general_ci;
insert into user(name,password,email,birthday) values('zs','123456','zs@sina.com','1980-12-04');
insert into user(name,password,email,birthday) values('lisi','123456','lisi@sina.com','1981-12-04');
insert into user(name,password,email,birthday) values('wangwu','123456','wangwu@sina.com','1979-12-04');
mysql> select * from user;
+----+--------+----------+-----------------+------------+
| id | name | password | email | birthday |
+----+--------+----------+-----------------+------------+
| 1 | zs | 123456 | zs@sina.com | 1980-12-04 |
| 2 | lisi | 123456 | lisi@sina.com | 1981-12-04 |
| 3 | wangwu | 123456 | wangwu@sina.com | 1979-12-04 |
+----+--------+----------+-----------------+------------+
package com.lmd.util;
import java.io.FileNotFoundException/FileReader;
import java.sql.Connection/DriverManager/ResultSet;
import java.sql.SQLException/Statement;
import java.util.Properties;
public class JDBCUtils {
private static Properties prop = null;
private JDBCUtils(){ }
//只解析一次,放在静态代码块里
static{
prop = new Properties();
try {
prop.load(new FileReader(JDBCUtils.class.getClassLoader().
getResource("config.properties").getPath()));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 获取连接
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConn() throws ClassNotFoundException, SQLException{
//1、注册数据库驱动
Class.forName(prop.getProperty("driver"));
//2、获取连接
return DriverManager.getConnection(prop.getProperty("url"),
prop.getProperty("username"), prop.getProperty("password"));
}
/**
* 关闭连接
* @param rs
* @param stat
* @param conn
*/
public static void close(ResultSet rs, Statement stat, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
conn = null;
}
}
}
}
配置文件:config.properties 在src下
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day10
username=root
password=66666
1、使用executeUpdate(String sql)方法完成数据添加操作,示例操作:
@Test
public void add(){
Connection conn = null;
Statement stat = null;
try {
//1、注册数据库驱动;2、获取数据库连接
conn = JDBCUtils.getConn();
//3、获取传输器对象
stat = conn.createStatement();
//4、利用传输器传输sql语句到数据库中执行,获取结果集对象
int count = stat.executeUpdate("insert into user values(null,'lily','666666'
,'lily8@163.com','1990-12-22')");
//5、处理结果
if (count>0) {
System.out.println("执行成功!影响到的行数为"+count);
} else {
System.out.println("执行失败!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6、关闭连接
JDBCUtils.close(null, stat, conn);
}
}
结果如下:
2、使用executeUpdate(String sql)方法完成数据修改操作,示例操作:
public void update(){
Connection conn = null;
Statement stat = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
stat.executeUpdate("update user set password='888888' where id=2");
} catch (Exception e) {
e.printStackTrace();
} finally {
//6、关闭连接
JDBCUtils.close(null, stat, conn);
}
}
结果如下:
3、使用executeQuery(String sql)方法完成数据查询操作,示例操作:
@Test
public void find(){
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
rs = stat.executeQuery("select * from user where name='lily'");
while (rs.next()) {
String name = rs.getString("name");
String password = rs.getString("password");
System.out.println(name+":"+password);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6、关闭连接
JDBCUtils.close(null, stat, conn);
}
}
结果如下:
4、使用 executeUpdate(String sql)方法完成数据删除操作,示例操作:
@Test
public void delete(){
Connection conn = null;
Statement stat = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
stat.executeUpdate("delete from user where name='lily'");
} catch (Exception e) {
e.printStackTrace();
} finally {
//6、关闭连接
JDBCUtils.close(null, stat, conn);
}
}
结果如下:
(四)、案例:改写前面讲解的用户注册和登陆案例,实现如下需求:
1、把xml换成数据库,重写XmlUserDao。
2、定义DAO接口,并定义Dao工厂,实现service层和dao层的解耦。
3、自定义dao异常。
4、防范sql注入攻击
一、为什么:要分层
使软件具有结构性,便于开发、维护和管理。
将不同功能模块独立,在需要替换某一模块时不需要改动其他模块,方便代码的复用、替换
二、层与层耦合的概念,利用工厂类解耦
在分层结构中,我们希望将各个功能
约束在各自的模块(层)当中的,而当属于某一层的对象、方法“入侵”到了其他层,如将web层的ServletContext对象
传入service层,或service层调用XMLDao独有的方法,就会导致层与层之间的关系过于“紧密”,当需要修改某一层时不可
避免的要修改其他关联的层,这和我们软件分层最初的设想-----层与层分离,一个层尽量不依赖其他层存在,
当修改一层时无需修改另一层的设想是违背的。这种“入侵”造成的“紧密”关系就早做层与层之间发生的“耦合”,
而去掉这种耦合性的过程就叫做层与层之间“解耦”
利用工厂类可以实现解耦的功能
三、如何判断一项功能到底属于哪一层
某一项功能属于哪一层,往往是不能明确确定出来的,这时可以参考如下标准进行判断:
1、此项功能在业务逻辑上更贴近与哪一层,放在哪一层更能较少耦合
2、此项功能是否必须使用某一层特有的对象
3、如果放在哪一层都可以,那么放在哪一层更方便技术上的实现,及方便代码的编写和维护
四、异常的处理
1、如果一个异常抛给上一层会增加程序的耦合性,请当场解决:如将xml解析错误抛给service层,那么当换成mysqldao时,
还需要修改service去掉xml解析异常的处理。
2、如果上一层明确需要此异常进行代码的流转,请抛出:如当查找一个用户信息而用户找不到时,
可以抛出一个用户找不到异常,明确要求上一层处理。
3、如果这一层和上一层都能解决尽量在这一层解决掉。
4、如果这一层不能解决,而上一层能解决抛给上一层。
5、如果所有层都不能解决,则应抛出给虚拟机使线程停止,但是如果直接抛出这个异常,
则还需要调用者一级一级继续- 往上抛出最后才能抛给虚拟机,所以还不如在出现异常的位置直接try...catch住后转换为RuntimeException抛出。
:如读取配置文件出错,任何层都不能解决,转为RuntimeException抛出,停止线程。
1、改写案例
(1)、把xml换成数据库,重写XmlUserDao。
create table users(
id int primary key auto_increment,
username varchar(20),
password varchar(50),
nickname varchar(40),
email varchar(50)
);
insert into users values(null,'admin','admin','admin','admin8@qq.com');
(2)、重写XmlUserDao,改为MysqlUserDao.java。
1)、将XmlUserDao中的方法定义到一个接口中,注意:
使用接口定义使用的三个方法;接口一定要有注释,并且是文档注释。
方法注释一方都放在接口中,而非实现类中。
在接口中使用接口注释后,当鼠标移到实现类中方法上就会有提示。
package com.lmd.dao;
- import com.lmd.domain.User;
- public interface UserDao {
/**
* 根据用户名查找用户
* @param username 用户名
* @return 根据用户名找到的用户信息bean,若没找到返回null
*/
public User findUserByUserName(String username);
/**
* 添加用户
* @param user 要添加的用户信息bean
*/
public void addUser(User user);
/**
* 根据用户名和密码查找对应的用户
* @param username 用户名
* @param password 密码
* @return 找到的用户,若没找到返回null
*/
public User findUserByUNandPSW(String username, String password);
}
2)、仍需要前面设置的config.properties和JDBCUtils.java,开发MysqlUserDao.java:
修改User.java,增加id;修改UserService.java一句话。
//private XmlUserDao dao = new XmlUserDao();
private MysqlUserDao dao = new MysqlUserDao();
这个切换数据库会改变代码,不太好,后面会讲怎么办? 这是一种耦合,后面会利用工厂类实现解耦。
package com.lmd.dao;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import com.lmd.domain.User;
import com.lmd.util.JDBCUtils;
public class MysqlUserDao implements UserDao{
@Override
public User findUserByUserName(String username) {
String sql = "select * from users where username='"+username+"'";
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
rs = stat.executeQuery(sql);
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setNickname(rs.getString("nickname"));
user.setEmail(rs.getString("email"));
return user;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JDBCUtils.close(rs, stat, conn);
}
}
@Override
public void addUser(User user) {
String sql = "insert into users values (null,'"+user.getUsername()+
"','"+user.getPassword()+"','"+user.getNickname()+"','"+user.getEmail()+"')";
Connection conn = null;
Statement stat = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
stat.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JDBCUtils.close(null, stat, conn);
}
}
@Override
public User findUserByUNandPSW(String username, String password) {
String sql = "select * from users where username='"+username+"' and password='"+password+"'";
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
rs = stat.executeQuery(sql);
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setNickname(rs.getString("nickname"));
user.setEmail(rs.getString("email"));
return user;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JDBCUtils.close(rs, stat, conn);
}
}
}
2、定义DAO接口,并定义Dao工厂,实现service层和dao层的解耦。
package com.lmd.factory;
import java.io.FileReader;
import java.util.Properties;
import com.lmd.dao.UserDao;
/**
* 单例模式:保证在整个应用程序的生命周期中,
* 任何一个时刻,单例类的实例只存在一个(也可以不存在)
* @author angel11288
*
*/
public class DaoFactory {
private static DaoFactory factory = new DaoFactory();
//配置文件只读一次
private static Properties prop = null;
static{
try {
prop = new Properties();
prop.load(new FileReader(DaoFactory.class.getClassLoader().
getResource("config.properties").getPath()));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private DaoFactory() {}
public static DaoFactory getFactory() {
return factory;
}
public UserDao getDao() {
try {
String cla = prop.getProperty("UserDao");//com.lmd.dao.MysqlUserDao
return (UserDao)Class.forName(cla).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
修改UserService:
//private XmlUserDao dao = new XmlUserDao();
//private MysqlUserDao dao = new MysqlUserDao();
private UserDao dao = DaoFactory.getFactory().getDao();
配置文件config.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day10
username=root
password=666666
UserDao=com.lmd.dao.MysqlUserDao
#UserDao=com.lmd.dao.XmlUserDao
3、自定义dao异常。
4、SQL注入攻击:定义DAO接口,并定义Dao工厂,实现service层和dao层的解耦。
(1)、由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做sql注入攻击。 (登陆之后,注销,再次回来,只有用户名也可以登录)
admin 'or' 1=1,密码空,也可以登录成功。
(2)、PreperedStatement是Statement的孩子,它的实例对象可以通过调用Connection.preparedStatement()
方法获得,相对于Statement对象而言:
1)、PreperedStatement可以避免SQL注入的问题。
2)、Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率。
3)、并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
PreparedStatement利用预编译的机制将sql语句的主干和参数分别传输给数据库服务器,从而使数据库分辨
的出哪些是sql语句的主干哪些是参数,这样一来即使参数中带了sql的关键字,数据库服务器也仅仅将他当
作参数值使用,关键字不会起作用,从而从原理上防止了sql注入的问题
package com.lmd.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import com.lmd.util.JDBCUtils;
public class JDBCDemo3 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConn();
//主干语句
ps = conn.prepareStatement("select * from user where name=? and password=?");
ps.setString(1, "lisi");
ps.setString(2, "888888");
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("email"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
}
}
(3)、PreparedStatement主要有如下的三个优点:
~1.可以防止sql注入。
~2.由于使用了预编译机制,执行的效率要高于Statement。
~3.sql语句使用?形式替代参数,然后再用方法设置?的值,比起拼接字符串,代码更加优雅。
二、JDBC应用
(一)、使用JDBC处理大数据
1、基础:
(1)、在实际开发中,程序需要把大文本 Text 或二进制数据 Blob保存到数据库。Text是mysql叫法,Oracle中叫Clob。
(2)、基本概念:大数据也称之为LOB(Large Objects),LOB又分为:clob和blob
1)、clob用于存储大文本。Text
2)、blob用于存储二进制数据,例如图像、声音、二进制文等。
(3)、对MySQL而言只有blob,而没有clob,mysql存储大文本采用的是Text,Text和blob分别又分为:
1)、Text:TINYTEXT(255)、TEXT(64k)、MEDIUMTEXT(16M)和LONGTEXT(4G)
2)、Blob:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
2、使用JDBC处理大文本:
(1)、对于MySQL中的Text类型,可调用如下方法设置:
PreparedStatement.setCharacterStream(index, reader, length);
//注意length长度须设置,并且设置为int型
//当包过大时修改配置:[mysqld] max_allowed_packet=64M
(2)、对MySQL中的Text类型,可调用如下方法获取:
reader = resultSet. getCharacterStream(i);
等价于
reader = resultSet.getClob(i).getCharacterStream()
package com.lmd.job;
/*
* create table textdemo(
* id int primary key auto_increment,
* name varchar(20),
* content MEDIUMTEXT
* );
*/
import java.io.File
/FileReader;import java.sql.Connection
/PreparedStatement
/ResultSet;- import org.junit.Test;
- import com.lmd.util.JDBCUtils;
public class TextDemo1 {
@Test
public void addText() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("insert into textdemo values (null, ?,?)");
ps.setString(1, "三生三世十里桃花.txt");
File file = new File("三生三世十里桃花.txt");
ps.setCharacterStream(2, new FileReader(file), file.length());
ps.executeUpdate();
} catch(Exception e){
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
}
}
修改java虚拟机启动内存大小:
查看安装目录下的myeclipse.ini:
#utf8 (do not remove)
-startup
plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.300.v20150602-1417
-vm
D:\Program Files\MyEclipse2016\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\bin\javaw.exe
-configuration
D:\Program Files\MyEclipse2016\configuration
-install
D:\Program Files\MyEclipse2016
-vmargs
-Xmx1024m
-XX:ReservedCodeCacheSize=64m
-Dosgi.nls.warnings=ignore
异常处理
1.Exception in thread "main" java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setCharacterStream
(ILjava/io/Reader;J)V
ps.setCharacterStream(2, new FileReader(file), file.length());- 第三个参数是long型的是从1.6才开始支持的,驱动里还没有开始支持。
解决方案:ps.setCharacterStream(2, new FileReader(file), (int)file.length());
2.Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
文件大小过大,导致PreparedStatement中数据多大占用内存,内存溢出
-Xms256M-Xmx256M
3.com.mysql.jdbc.PacketTooBigException: Packet for query is too large (10886466 > 1048576).
You can change this value on the server by setting the max_allowed_packet' variable.
数据库连接传输用的包不够大,传输大文本时报此错误
在my.ini中配置max_allowed_packet指定包的大小
1、修改java虚拟机启动内存大小:
CMD修改:java -Xms 64M -Xmx 128M
MyEclipse中Run-->Run config...-->Arugument-->VMargument:-Xms64m 换行 -Xmx 128m
2、com.mysql.jdbc.PacketTooBigException: Packet for query is too large (6882307 > 4194304).
You can change this value on the server by setting the max_allowed_packet' variable.
修改C:\ProgramData\MySQL\MySQL Server 5.6下的my.ini:在[msqld]中写
public void findText() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("select * from textdemo where id=?");
ps.setInt(1,2);
rs = ps.executeQuery();
while (rs.next()) {
String name = rs.getString("name");
System.out.println(name);//三生三世十里桃花.txt
Reader reader = rs.getCharacterStream("content");
Writer writer = new FileWriter(name);
char[] chs = new char[1024];
int len = 0;
while ((len = reader.read(chs))!=-1) {
writer.write(chs, 0, len);
}
reader.close();
writer.close();
}
} catch(Exception e){
e.printStackTrace();
} finally {
JDBCUtils.close(rs, ps, conn);
}
}
3、使用JDBC处理二进制数据:
(1)、对于MySQL中的BLOB类型,可调用如下方法设置:
PreparedStatement. setBinaryStream(i, inputStream, length);
(2)、对MySQL中的BLOB类型,可调用如下方法获取:
InputStream in = resultSet.getBinaryStream(i);
InputStream in = resultSet.getBlob(i).getBinaryStream();
@Test
public void addBlob() {
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("insert into blobdemo values (null,?,?)");
ps.setString(1, "欢迎您.mp3");
File file = new File("1.mp3");
ps.setBinaryStream(2, new FileInputStream(file), file.length());
ps.executeUpdate();
}
}
@Test
public void findBlob() {
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("select * from blobdemo where id=?");
ps.setInt(1,1);
rs = ps.executeQuery();
while (rs.next()) {
String name = rs.getString("name");
System.out.println(name);//欢迎您.mp3
InputStream input = rs.getBinaryStream("content");
OutputStream output = new FileOutputStream(name);
byte[] bys = new byte[1024];
int len = 0;
while ((len = input.read(bys))!=-1) {
output.write(bys, 0, len);
}
input.close();
output.close();
}
}
但是:图片和音乐等一般不会存在数据库中,只需要知道就行。大文件一般放在服务器的硬盘上,访问的是服务器存在的
文件的硬盘路径,通过读路径得到大文件。
3、使用JDBC进行批处理:
(1)、业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的
批处理机制,以提升执行效率。
(2)、实现批处理有两种方式,第一种方式:
1)、void Statement.addBatch(sql) 。
2)、执行批处理SQL语句。
3)、int[] executeBatch()方法:执行批处理命令。返回包含批中每个命令的一个元素的更新计数所组成的数组。
4)、void clearBatch()方法:清除批处理命令 。
采用Statement.addBatch(sql)方式实现批处理:
优点:可以向数据库发送多条不同的SQL语句。
缺点:SQL语句没有预编译。当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。
package com.lmd.batch;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import com.lmd.util.JDBCUtils;
public class StatementBatch {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
try {
conn = JDBCUtils.getConn();
stat = conn.createStatement();
stat.addBatch("create database day10batch");
stat.addBatch("use day10batch");
stat.addBatch("create table batchdemo("
+"id int primary key auto_increment,"
+ "name varchar(20))");
stat.addBatch("insert into batchdemo values(null,'aaa')");
stat.addBatch("insert into batchdemo values(null,'bbb')");
stat.addBatch("insert into batchdemo values(null,'ccc')");
stat.addBatch("insert into batchdemo values(null,'ddd')");
stat.executeBatch();//返回int类型的数组
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null, stat, conn);
}
}
}
结果如下:
mysql> use day10batch;
Database changed
mysql> select * from batchdemo;
+----+------+
| id | name |
+----+------+
| 1 | aaa |
| 2 | bbb |
| 3 | ccc |
| 4 | ddd |
+----+------+
4 rows in set
mysql>
(3)、实现批处理有两种方式,第一种方式: PreparedStatement.addBatch()
采用PreparedStatement.addBatch()实现批处理,将一组参数添加到此 PreparedStatement 对象的批处理命令中。
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
package com.lmd.batch;
- import java.sql.Connection;
import java.sql.PreparedStatement;
- import com.lmd.util.JDBCUtils;
- /*
create table psbatch(
id int primary key auto_increment,
name varchar(20)
);
*/
public class PreparedStatementBatch {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("insert into psbatch values(null,?)");
for (int i = 0; i < 100001; i++) {
ps.setString(1, "name"+i);
ps.addBatch();
if (i%1000==0) {
ps.executeBatch();
ps.clearBatch();
}
}
ps.executeBatch();//为了剩下的那一条记录
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null, ps, conn);
}
}
}
结果如下:
mysql> select count(*) from psbatch;
+----------+
| count(*) |
+----------+
| 419 |
+----------+
1 row in set
mysql> select count(*) from psbatch;
+----------+
| count(*) |
+----------+
| 689 |
+----------+
1 row in set
mysql>