一 JDBC(Java数据库连接)编写步骤
1)导入java.sql包
2)加载并注册驱动程序
3)创建Connection对象
4)创建Statement对象
5)执行SQL语句
- 5-1)查询使用ResultSet对象接收结果
- 5-2)关闭ResultSet对象
6)关闭Connection对象
二 连接方式
1 方式一
Driver driver = new com.mysql.jdbc.Driver();
String url = "";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
Connection conn = driver.connect(url,info);
2 方式二
1 获取Driver实现类对象,使用反射。不出现第三方api,使程序有更好的可移植性。
// 1 获取driver实现类的对象
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
String url = "";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
Connection conn = driver.connect(url,info);
3 方式三
DriverManager替换Driver
// 1 获取driver实现类的对象
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 2 提供另外三个连接的基本信息
String url = "";
String user = "";
String password = "";
// 注册驱动
DriverManager.registerDriver(driver);
// 获取连接
DriverManager.getConnection(url,user,password)
4 方式四
相比方式三,可以省略两步操作。在mysql的Driver实现类中,声明了静态代码块。(只加载,自动注册)
// 1 提供另外三个连接的基本信息
String url = "";
String user = "";
String password = "";
// 1 获取driver实现类的对象
Class.forName("com.mysql.jdbc.Driver");
// Driver driver = (Driver) clazz.newInstance();
// // 注册驱动
// DriverManager.registerDriver(driver);
// 获取连接
DriverManager.getConnection(url,user,password);
5 方式五
将数据库连接需要的四个基本信息声明在配置文件中,通过读取文件获取信息再进行连接。
配置文件名:jdbc.properties
user=root
password=123456
url=
driverClass=com.mysql.jdbc.Driver
代码:
// 1 读取信息
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty(user);
String password = pros.getProperty(password);
String url = pros.getProperty(url);
String driverClass = pros.getProperty(driverClass);
// 2 加载驱动
Class.forName(driverClass);
// 3 获取连接
DriverManager.getConnection(url,user,password);
6 总结
四种方案层层递进,最后一种的好处在于,实现了数据和代码的分离,实现了解耦。
三 JDBCUtils工具类
public class JDBCUtils {
// 获取连接
public static Connection getConnection() throws Exception {
// 1 读取信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("jdbc.username");
String password = pros.getProperty("jdbc.password");
String url = pros.getProperty("jdbc.url");
String driverClass = pros.getProperty("jdbc.driver");
// 2 加载驱动
Class.forName(driverClass);
// 3 获取连接
Connection connection = DriverManager.getConnection(url,user,password);
return connection;
}
// 关闭资源
public static void closeResource(Connection conn, Statement ps) {
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
四 Statement操作数据库弊端
存在sql注入问题
String sql = "select user,password from user_table where user = '" + user + "'and password = '" + password + "'";
结果sql,永远登陆成功
select user,password from user_table where user = '1' or 'and password = '=1 or '1' = '1'
五 PrepareStatement
1 PrepareStatement实现通用的增删改操作
ORM编程思想:Object Relational Mapping对象关系映射 是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
- 一个表对应一个类
- 一个记录对应一个对象
- 一个字段对应一个属性
// 通用的增删改操作
public void update(String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
try {
// 1 获取数据库连接
conn = JDBCUtils.getConnection();
// 2 预编译sql语句
ps = conn.prepareStatement(sql);
// 3 填充占位符
for(int i = 0; i < args.length;i++){
ps.setObject(i + 1,args[i]);
}
// 4 执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
// 5 资源关闭
JDBCUtils.closeResource(conn,ps);
}
2 PrepareStatement实现通用的查询操作(返回一条记录)
// 通用的查询操作
public User query(String sql, Object...args) throws Exception {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// 补充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 执行,并返回一个结果集
rs = ps.executeQuery();
// 获取结果集元数据(列数封装在结果集的一个元数据ResultSetMetaData【修饰现有数据的数据】里了)
ResultSetMetaData rsmd = rs.getMetaData();
// 通过元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
// 是否有数据
if (rs.next()) {
User user = new User();
// 处理一行结果中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列名
String columnName = rsmd.getColumnName(i + 1);
// 给user对象指定某个属性,通过反射
Field field = User.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(user, columnValue);
}
return user;
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
测试:
@Test
public void test() throws Exception {
String sql = "select uid,username,password from user_user where uid = ?";
User user = query(sql , 4);
System.out.println(user);
}
3 PrepareStatement实现针对于不同表的查询操作(返回一条记录)
// PrepareStatement实现针对于不同表的查询操作
public <T> T getInstance(Class<T> clazz,String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// 补充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 执行,并返回一个结果集
rs = ps.executeQuery();
// 获取结果集元数据(列数封装在结果集的一个元数据ResultSetMetaData【修饰现有数据的数据】里了)
ResultSetMetaData rsmd = rs.getMetaData();
// 通过元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
// 是否有数据
if (rs.next()) {
T t = clazz.newInstance();
// 处理一行结果中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列名
String columnName = rsmd.getColumnName(i + 1);
// 给user对象指定某个属性,通过反射
Field field = User.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
测试:
@Test
public void testGetInstance(){
String sql = "select uid,username,password from user_user where uid=?";
User user = getInstance(User.class, sql, 3);
System.out.println(user);
}
4 PrepareStatement实现针对于不同表的查询操作(返回多条记录)
public <T> List<T> getForList(Class<T> clazz, String sql, Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// 补充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 执行,并返回一个结果集
rs = ps.executeQuery();
// 获取结果集元数据(列数封装在结果集的一个元数据ResultSetMetaData【修饰现有数据的数据】里了)
ResultSetMetaData rsmd = rs.getMetaData();
// 通过元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
// 创建集合对象
ArrayList<T> list = new ArrayList<T>();
// 是否有数据
while (rs.next()) {
T t = clazz.newInstance();
// 处理一行结果中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列名
String columnName = rsmd.getColumnName(i + 1);
// 给user对象指定某个属性,通过反射
Field field = User.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
测试:
@Test
public void testgetForList(){
String sql = "select uid,username,password from user_user where uid < ?";
List<User> list = getForList(User.class, sql, 10);
list.forEach(System.out::println);
}
5 总结
- 查询 不同于 增删改 ,因为我们需要接收查询出来的数据,从而有了结果集ResultSet。
- PreparedStatement为什么解决了SQL注入问题:因为逻辑关系已经被预编译过了。
- PreparedStatement可以操作Blob类型数据。
- PreparedStatement可以实现更高效的批量插入。
六 数据库存图片
1 mysql可使用的数据类型
- 如果存储的文件过大,数据库的性能会下降。
2 插入blob类型数据
代码:
@Test
public void testInsert() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = "insert into user_user(uid,username,password,photo)values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,6);
ps.setObject(2,"金泰梨");
ps.setObject(3,"0000");
FileInputStream is = new FileInputStream(new File("jintaili.jpg"));
ps.setBlob(4,is);
ps.execute();
JDBCUtils.closeResource(conn,ps);
}
navicat展示:
3 读取blob类型数据
代码:
@Test
public void testQuery() {
Connection conn = null;
String sql = "select uid,username,password,photo from user_user where uid = ?";
PreparedStatement ps = null;
InputStream is = null;
FileOutputStream fos = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
ps.setObject(1,6);
rs = ps.executeQuery();
if(rs.next()){
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
User user = new User(uid, username, password);
System.out.println(user);
// 以 流 的方式获取
Blob photo = rs.getBlob("photo");
// 下载下来保存到本地
is = photo.getBinaryStream();
fos = new FileOutputStream("jintaili_back.jpg");
byte[] buffer = new byte[1024];
int len;
while ( (len = is.read(buffer)) != -1 ) {
fos.write(buffer, 0, len);
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 关闭资源
JDBCUtils.closeResource(conn,ps,rs);
try{
if (is != null){
is.close();
}
}catch (IOException e){
e.printStackTrace();
}
try{
if (fos != null){
fos.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
查询的图片保存到工程下了:
4 插入blob字段特情况说明
当插入的数据超过1M会报错,不让放。
虽然mediumblob最大是16M,但默认是1M。
所以需要修改一个文件MYSQL文件夹里面的my.ini
加入:
max_allowed_packet=16M
重启服务,才能生效。
七 批量插入数据
1 使用ps.addBatch()
代码:
@Test
public void testInsert(){
Connection conn = null;
PreparedStatement ps = null;
try{
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
String sql = "insert into goods(id)values(?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 2000 ; i++) {
ps.setObject(1, i);
// 1 攒sql
ps.addBatch();
if(i % 500 == 0){
// 2 攒够500执行一次
ps.executeBatch();
// 3 清空
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn,ps);
}
}
2 优化:设置不允许自动提交数据
代码:
@Test
public void testInsert(){
Connection conn = null;
PreparedStatement ps = null;
try{
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
// 设置不允许自动提交数据
conn.setAutoCommit(false);
String sql = "insert into goods(id)values(?)";
ps = conn.prepareStatement(sql);
for (int i = 1; i <= 2000 ; i++) {
ps.setObject(1, i);
// 1 攒sql
ps.addBatch();
if(i % 500 == 0){
// 2 攒够500执行一次
ps.executeBatch();
// 3 清空
ps.clearBatch();
}
}
// 提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println(end - start);
}catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn,ps);
}
}
大概节省1/3时间。
八 PrepareStatement vs Statement
- PrepareStatement 和 Statement是两个接口,PrepareStatement 是 Statement 子接口。
- Statement 像一个信使,携带java里的String类型sql语句到数据库。
- 实际使用 PrepareStatement 替换Statement,因为Statement存在两个弊端:
- 1 存在拼串操作,繁琐
- 2 sql注入问题
- PrepareStatement 可以实现对blob类型的操作。
- PrepareStatement 可以预编译,好处有:
- 1 更高效
- 2 防止sql注入问题