JavaWeb基础 - 跟JDBC说拜拜!

大家好,我是一只学弱狗,记录学习的点点滴滴!

优质文章

  • 一张黄图的故事
  • JavaSE练习项目
  • 我是菜鸟、我小试牛刀
  • linux指令太多记不住?小白看这篇就够了!

优质专栏

  • 数据库就该这样学
  • 爪哇外步篇

谨以此篇博客,结束时长整8个月的寒暑假生活。

畅聊JDBC

    • JDBC
      • 概念
      • 使用步骤
      • 代码演示
      • 相关对象详解
        • 疑问猜测
        • DriverManager
          • 概念
          • 功能
            • 注册驱动
            • 获取数据库连接
        • Connection
          • 概念
          • 功能
            • 获取执行sql的对象
            • 管理事务
        • Statement
          • 概念
            • 常用方法
        • ResultSet
          • 概念
            • 常用方法
      • 案例:登陆验证功能
      • 案例思考
      • 事务管理及演示
        • 什么是事务?
        • 模拟:转账
    • 数据库连接池
      • 概念
      • 剖析
      • C3P0数据库连接池
        • 使用步骤
      • Druid数据库连接池
        • 使用步骤
    • Spring JDBC
      • 概念
      • 常用方法

JDBC

概念

JDBC,是Java Database Connectivity的缩写,即Java数据库连接,先看搜狗百科上的解释:JDBC是一种用于执行SQL语句的Java API,由一组用Java语言编写的类和接口组成,它可以为多种关系型数据库提供统一访问,据此可以构建更高级的工具和接口,实现了所有这些面向标准的目标并且具有简单,严格类型定义且高性能实现的接口。
上面的解释我勉强接受,用简单的语言我们这样来描述:它是Sun公司定义的一套操作所有关系型数据库的规则,各个数据库厂商,像MySQL、SQL Server等等,他们自己去实现这些接口,提供数据库的驱动jar包,我们可以按照Sun公司的规则(接口)编程。

使用步骤

  1. 导入驱动jar包(或者使用maven构建)
  2. 注册驱动
  3. 获取数据库连接对象
  4. 定义sql语句
  5. 获取执行sql语句的对象
  6. 执行sql语句,接收返回结果
  7. 处理结果
  8. 释放资源

Maven依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version>
</dependency>

代码演示

public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;//1.导入jar包try {//2.注册驱动Class.forName("com.mysql.jdbc.Driver");//3.获取连接对象connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "mysql");//4.定义sql语句String sql = "select * from tbl_user";//5.获取执行sql语句的对象statement = connection.createStatement();//6.执行sql语句resultSet = statement.executeQuery(sql);//7.处理结果while (resultSet.next()) {String username = resultSet.getString("username");String password = resultSet.getString("password");System.out.println(username + " : " + password);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();} finally {//8.释放资源if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}}

相关对象详解

疑问猜测

看过了上面的代码,有小伙伴不禁会问,注册驱动,不就是通过反射加载Driver类进内存么,怎么就注册驱动了啊?
其实刚开始,我也有这样的疑问,不妨打开源码看一下
噢,原来这儿是一个静态代码块,懂了懂了。

DriverManager

概念

驱动管理对象

功能
注册驱动

在Driver的静态代码块中,我们发现,通过DriverManager的静态方法registerDriver来注册的驱动

获取数据库连接
public static Connection getConnection​(String url,String user,String password)
  • url:指定连接路径
  • user:用户名
  • password:密码

Connection

概念

数据库连接对象

功能
获取执行sql的对象
Statement createStatement​() 	//执行静态sql
PreparedStatement prepareStatement​(String sql)	//执行预编译sql 
管理事务
//开启事务
void setAutoCommit​(boolean autoCommit) //调用该方法设置参数为false,即开启事务
//提交事务
void commit​() 
//回滚事务
void rollback​() 

Statement

概念

用于执行静态sql语句并返回其生成结果的对象

常用方法
int executeUpdate​(String sql) //执行增删改语句,返回影响的行数
ResultSet executeQuery​(String sql) //执行查语句,返回ResultSet结果集对象

ResultSet

概念

结果集对象,封装查询结果

常用方法
boolean next​() //游标向下移动一行,判断当前行是否是数据行,如果是数据行则返回true,否则返回false
XXX getXXX() //获取数据,该方法为重载方法,可以传入列数,也可以传入列名

案例:登陆验证功能

任何理论都离不开实践!
需求:用户在控制台输入用户名和密码,核对其正确性,整个过程存储为日志信息。

    public static void main(String[] args) {//待输入用户名String username = null;//待输入密码String password = null;//获取键盘输入流对象Scanner in = new Scanner(System.in);//日期对象Date date = new Date();//日期格式化对象SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");//缓冲字符输出流BufferedWriter bw = null;//数据库连接对象Connection connection = null;//执行sql语句对象Statement statement = null;//结果集对象ResultSet resultSet = null;try {//获取类加载器ClassLoader classLoader = Demo.class.getClassLoader();//获取指定资源的URL路径URL resource = classLoader.getResource("conf/log.txt");//获取指定资源的绝对路径String path = resource.getPath();//文件字符输出流 追加字符FileWriter fw = new FileWriter(path, true);bw = new BufferedWriter(fw);System.out.println("用户名:");username = in.nextLine();System.out.println("密码:");password = in.nextLine();connection = JDBCUtils.getConnection();String sql = "select * from tbl_user where username = '" + username + "' and password = '" + password + "' ";System.out.println(sql);statement = connection.createStatement();resultSet = statement.executeQuery(sql);String format = sdf.format(date);bw.newLine();bw.write("时间:" + format);bw.newLine();bw.write("用户名:" + username);bw.newLine();bw.write("密码:" + password);bw.newLine();if (resultSet.next()) {System.out.println("登录成功!");bw.write("登录成功!");} else {System.out.println("登录失败!");bw.write("登录失败!");}bw.newLine();bw.write("--------------------------------------");} catch (FileNotFoundException e) {System.out.println("未发现指定文件");System.exit(-1);} catch (SQLException e) {System.out.println("获取statement对象异常");System.exit(-1);} catch (IOException e) {System.out.println("IO读写异常");System.exit(-1);} finally {JDBCUtils.close(connection, statement, resultSet);try {bw.close();} catch (IOException e) {e.printStackTrace();}}}

注意到,有个JDBCUtils类,我们看下这个类

public class JDBCUtils {private static String driver = null;private static String url = null;private static String user = null;private static String password = null;static {try {Properties properties = new Properties();/*** 获取src路径下的文件的方式-->ClassLoader 类加载器*/properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/jdbc.properties"));driver = properties.getProperty("driver");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");Class.forName(driver);} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}public static Connection getConnection() {try {Connection connection = DriverManager.getConnection(url,user,password);return connection;} catch (SQLException e) {e.printStackTrace();return null;}}public static void close(Connection connection, Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}public static void close(Connection connection, Statement statement, ResultSet resultSet){close(connection,statement);if(resultSet!=null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}
}

仔细观察,提高了代码的复用性,通过配置文件修改数据库的属性,是不是值得我们学习呢?

案例思考

上面的代码,还是很不错的,但是也面临着一个问题,sql注入问题
纳尼?不可思议,观察sql语句,对于聪明的你我就不做更多的解释了?如何解决?PreparedStatement,之前我们说过,它是一个执行预编译sql的对象,如何操作呢?
首先原先的步骤得先发生下改变

  1. 导入驱动jar包
  2. 注册驱动
  3. 获取数据库连接对象 Connection
  4. 定义sql语句,注:sql的参数使用?来代替
  5. 获取执行sql语句的对象PreparedStatement
  6. 给占位符?赋值
  7. 执行sql语句,接收返回结果
  8. 处理结果
  9. 释放资源
    于是,我们有个加强版
    public static void main(String[] args) {String username = null;String password = null;Scanner in = new Scanner(System.in);Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");BufferedWriter bw = null;Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {ClassLoader classLoader = DemoPlus.class.getClassLoader();URL resource = classLoader.getResource("conf/log.txt");String path = resource.getPath();FileWriter fw = new FileWriter(path, true);bw = new BufferedWriter(fw);/*  FileOutputStream fos = new FileOutputStream(path,true);OutputStreamWriter osw = new OutputStreamWriter(fos);BufferedWriter bw = new BufferedWriter(osw);*/System.out.println("用户名:");username = in.nextLine();System.out.println("密码:");password = in.nextLine();connection = JDBCUtils.getConnection();String sql = "select * from tbl_user where username = ? and password = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1,username);preparedStatement.setString(2,password);resultSet = preparedStatement.executeQuery();String format = sdf.format(date);bw.newLine();bw.write("时间:" + format);bw.newLine();bw.write("用户名:" + username);bw.newLine();bw.write("密码:" + password);bw.newLine();if (resultSet.next()) {System.out.println("登录成功!");bw.write("登录成功!");} else {System.out.println("登录失败!");bw.write("登录失败!");}bw.newLine();bw.write("--------------------------------------");} catch (FileNotFoundException e) {System.out.println("未发现指定文件");System.exit(-1);} catch (SQLException e) {System.out.println("获取statement对象异常");System.exit(-1);} catch (IOException e) {System.out.println("IO读写异常");System.exit(-1);} finally {JDBCUtils.close(connection, preparedStatement, resultSet);try {bw.close();} catch (IOException e) {e.printStackTrace();}}}

我们再来sql注入一次,没门。。。

事务管理及演示

什么是事务?

指一个包含多个步骤的业务操作,如果这个业务被事务管理,则这多个步骤要么同时执行成功,要么同时失败。

模拟:转账

构建数据库表

我们的需求是张三给李四转1000元,期望结果张三余额是4000元,李四余额是6000元。

    public static void main(String[] args) {//获取数据库连接对象Connection connection = JDBCUtils.getConnection();try {//开启事务connection.setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}//定义sql语句String sql = "update tbl_account set balance = balance + ? where id = ? ";//获取执行预编译sql语句的PreparedStatement对象PreparedStatement preparedStatement1 = null;PreparedStatement preparedStatement2 = null;try {preparedStatement1 = connection.prepareStatement(sql);preparedStatement1.setDouble(1,-1000);preparedStatement1.setInt(2,1);preparedStatement2 = connection.prepareStatement(sql);preparedStatement2.setDouble(1,+1000);preparedStatement2.setInt(2,2);int count1 = preparedStatement1.executeUpdate();//此处发生错误int a = 3/0;int count2 = preparedStatement2.executeUpdate();//提交事务connection.commit();if(count1>0 && count2>0){System.out.println("转账成功!");}else{System.out.println("转账失败!");}} catch (SQLException e) {try {//事务回滚connection.rollback();} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace();}finally {JDBCUtils.close(connection,preparedStatement1);JDBCUtils.close(null,preparedStatement2);}}

以上代码,感兴趣慢慢研究,如果去掉了事务管理,会怎么样呢?

数据库连接池

概念

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

剖析

Java官方提供了DataSource接口,该接口由数据库厂商实现,用于构建数据库连接池,可以使用其方法来过去连接对象或者归还连接

C3P0数据库连接池

使用步骤

  1. 导入jar包
  2. 编写配置文件(注:文件必须是c3p0.properties或c3p0-config.xml)
  3. 通过ComboPooledDataSource 创建DataSource对象
  4. 获取连接Connection对象
  5. 编写sql语句,执行并处理结果
  6. 归还连接对象

配置文件

<c3p0-config><default-config><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property><property name="user">root</property><property name="password">mysql</property><!-- 初始化申请的连接数量 --><property name="initialPoolSize">10</property><!-- 最大的连接数量 --><property name="maxPoolSize">10</property><!-- 超时时间 --><property name="checkoutTimeout">3000</property></default-config><!-- This app is massive! --><named-config name="intergalactoApp"><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property><property name="user">root</property><property name="password">mysql</property><!-- 初始化申请的连接数量 --><property name="initialPoolSize">10</property><!-- 最大的连接数量 --><property name="maxPoolSize">10</property><!-- 超时时间 --><property name="checkoutTimeout">3000</property></named-config>
</c3p0-config>
public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.导入jar包//2.编写配置文件//3.通过ComboPooledDataSource创建DataSource对象DataSource dataSource = new ComboPooledDataSource();//4.获取连接对象connection = dataSource.getConnection();//5.编写sql语句,执行并处理结果String sql = "select * from tbl_account";preparedStatement = connection.prepareStatement(sql);resultSet = preparedStatement.executeQuery();while (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");double balance = resultSet.getDouble("balance");System.out.println(id + " : " + name + " : " + balance);}} catch (SQLException e) {e.printStackTrace();} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {//6.归还连接对象connection.close();} catch (SQLException e) {e.printStackTrace();}}}}

Druid数据库连接池

使用步骤

  1. 导入jar包
  2. 编写配置文件(注:不同于c3p0,该配置文件可自定义名称及位置,通过properties来读取它)
  3. 通过DruidDataSourceFactory 创建DataSource对象
  4. 获取连接Connection对象
  5. 编写sql语句,执行并处理结果
  6. 归还连接对象

配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=mysql
maxActive=10
maxWait=3000

public class JDBCUtils {private static DataSource dataSource = null;static {try {Properties properties = new Properties();properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/druid.properties"));dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}}public static DataSource getDataSource(){return dataSource;}public static Connection getConnection() throws SQLException {return dataSource.getConnection();}public static void close(Connection connection, Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}public static void close(Connection connection, Statement statement, ResultSet resultSet){close(connection,statement);if(resultSet!=null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}
}

Spring JDBC

概念

Spring框架对JDBC的简单封装,简化JDBC的开发

常用方法

  • 创建JdbcTemplate的对象,参数为一个数据库连接池DataSource对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(JDBCUtils.getDataSource());
  • 使用update方法来实现增删改,返回影响的行数
	String sql = "update tbl_account set balance = ? where id = ?";int count = jdbcTemplate.update(sql, balance, id);
	String sql = "insert into tbl_account values(null,?,?)";int count = jdbcTemplate.update(sql, name, balance);
	String sql = "delete from tbl_account where id = ?";int count = jdbcTemplate.update(sql, id);
  • 使用queryForMap 方法来返回数据,注:该查询的数据集的长度只能是1,列名作为key,值为value
	String sql = "select * from tbl_account where balance = ?";Map<String, Object> map = jdbcTemplate.queryForMap(sql,balance);
  • 使用queryForList 方法来返回数据集,注:该方式是先将每条数据封装为Map集合,再装载到List集合中
	String sql = "select * from tbl_account";List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
  • 使用queryForObject 方法来返回指定数值,注:该方式一般用于聚合函数的查询
	String sql = "select count(*) from tbl_account";Long total = jdbcTemplate.queryForObject(sql, long.class);
  • 使用query 方法来返回指定对象的数值

**方式一:**自己写RowMapper接口的方法
**方式二:**使用BeanPropertyRowMapper实现类

	String sql = "select * from tbl_account";/*List<Account> list = jdbcTemplate.query(sql,new RowMapper<Account>(){@Overridepublic Account mapRow(ResultSet resultSet, int i) throws SQLException {Account account = new Account();int id = resultSet.getInt("id");String name = resultSet.getString("name");double balance = resultSet.getDouble("balance");account.setId(id);account.setName(name);account.setBalance(balance);return account;}});*/List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));

JavaWeb基础 - 跟JDBC说拜拜!

大家好,我是一只学弱狗,记录学习的点点滴滴!

优质文章

  • 一张黄图的故事
  • JavaSE练习项目
  • 我是菜鸟、我小试牛刀
  • linux指令太多记不住?小白看这篇就够了!

优质专栏

  • 数据库就该这样学
  • 爪哇外步篇

谨以此篇博客,结束时长整8个月的寒暑假生活。

畅聊JDBC

    • JDBC
      • 概念
      • 使用步骤
      • 代码演示
      • 相关对象详解
        • 疑问猜测
        • DriverManager
          • 概念
          • 功能
            • 注册驱动
            • 获取数据库连接
        • Connection
          • 概念
          • 功能
            • 获取执行sql的对象
            • 管理事务
        • Statement
          • 概念
            • 常用方法
        • ResultSet
          • 概念
            • 常用方法
      • 案例:登陆验证功能
      • 案例思考
      • 事务管理及演示
        • 什么是事务?
        • 模拟:转账
    • 数据库连接池
      • 概念
      • 剖析
      • C3P0数据库连接池
        • 使用步骤
      • Druid数据库连接池
        • 使用步骤
    • Spring JDBC
      • 概念
      • 常用方法

JDBC

概念

JDBC,是Java Database Connectivity的缩写,即Java数据库连接,先看搜狗百科上的解释:JDBC是一种用于执行SQL语句的Java API,由一组用Java语言编写的类和接口组成,它可以为多种关系型数据库提供统一访问,据此可以构建更高级的工具和接口,实现了所有这些面向标准的目标并且具有简单,严格类型定义且高性能实现的接口。
上面的解释我勉强接受,用简单的语言我们这样来描述:它是Sun公司定义的一套操作所有关系型数据库的规则,各个数据库厂商,像MySQL、SQL Server等等,他们自己去实现这些接口,提供数据库的驱动jar包,我们可以按照Sun公司的规则(接口)编程。

使用步骤

  1. 导入驱动jar包(或者使用maven构建)
  2. 注册驱动
  3. 获取数据库连接对象
  4. 定义sql语句
  5. 获取执行sql语句的对象
  6. 执行sql语句,接收返回结果
  7. 处理结果
  8. 释放资源

Maven依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version>
</dependency>

代码演示

public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;//1.导入jar包try {//2.注册驱动Class.forName("com.mysql.jdbc.Driver");//3.获取连接对象connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "mysql");//4.定义sql语句String sql = "select * from tbl_user";//5.获取执行sql语句的对象statement = connection.createStatement();//6.执行sql语句resultSet = statement.executeQuery(sql);//7.处理结果while (resultSet.next()) {String username = resultSet.getString("username");String password = resultSet.getString("password");System.out.println(username + " : " + password);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();} finally {//8.释放资源if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}}

相关对象详解

疑问猜测

看过了上面的代码,有小伙伴不禁会问,注册驱动,不就是通过反射加载Driver类进内存么,怎么就注册驱动了啊?
其实刚开始,我也有这样的疑问,不妨打开源码看一下
噢,原来这儿是一个静态代码块,懂了懂了。

DriverManager

概念

驱动管理对象

功能
注册驱动

在Driver的静态代码块中,我们发现,通过DriverManager的静态方法registerDriver来注册的驱动

获取数据库连接
public static Connection getConnection​(String url,String user,String password)
  • url:指定连接路径
  • user:用户名
  • password:密码

Connection

概念

数据库连接对象

功能
获取执行sql的对象
Statement createStatement​() 	//执行静态sql
PreparedStatement prepareStatement​(String sql)	//执行预编译sql 
管理事务
//开启事务
void setAutoCommit​(boolean autoCommit) //调用该方法设置参数为false,即开启事务
//提交事务
void commit​() 
//回滚事务
void rollback​() 

Statement

概念

用于执行静态sql语句并返回其生成结果的对象

常用方法
int executeUpdate​(String sql) //执行增删改语句,返回影响的行数
ResultSet executeQuery​(String sql) //执行查语句,返回ResultSet结果集对象

ResultSet

概念

结果集对象,封装查询结果

常用方法
boolean next​() //游标向下移动一行,判断当前行是否是数据行,如果是数据行则返回true,否则返回false
XXX getXXX() //获取数据,该方法为重载方法,可以传入列数,也可以传入列名

案例:登陆验证功能

任何理论都离不开实践!
需求:用户在控制台输入用户名和密码,核对其正确性,整个过程存储为日志信息。

    public static void main(String[] args) {//待输入用户名String username = null;//待输入密码String password = null;//获取键盘输入流对象Scanner in = new Scanner(System.in);//日期对象Date date = new Date();//日期格式化对象SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");//缓冲字符输出流BufferedWriter bw = null;//数据库连接对象Connection connection = null;//执行sql语句对象Statement statement = null;//结果集对象ResultSet resultSet = null;try {//获取类加载器ClassLoader classLoader = Demo.class.getClassLoader();//获取指定资源的URL路径URL resource = classLoader.getResource("conf/log.txt");//获取指定资源的绝对路径String path = resource.getPath();//文件字符输出流 追加字符FileWriter fw = new FileWriter(path, true);bw = new BufferedWriter(fw);System.out.println("用户名:");username = in.nextLine();System.out.println("密码:");password = in.nextLine();connection = JDBCUtils.getConnection();String sql = "select * from tbl_user where username = '" + username + "' and password = '" + password + "' ";System.out.println(sql);statement = connection.createStatement();resultSet = statement.executeQuery(sql);String format = sdf.format(date);bw.newLine();bw.write("时间:" + format);bw.newLine();bw.write("用户名:" + username);bw.newLine();bw.write("密码:" + password);bw.newLine();if (resultSet.next()) {System.out.println("登录成功!");bw.write("登录成功!");} else {System.out.println("登录失败!");bw.write("登录失败!");}bw.newLine();bw.write("--------------------------------------");} catch (FileNotFoundException e) {System.out.println("未发现指定文件");System.exit(-1);} catch (SQLException e) {System.out.println("获取statement对象异常");System.exit(-1);} catch (IOException e) {System.out.println("IO读写异常");System.exit(-1);} finally {JDBCUtils.close(connection, statement, resultSet);try {bw.close();} catch (IOException e) {e.printStackTrace();}}}

注意到,有个JDBCUtils类,我们看下这个类

public class JDBCUtils {private static String driver = null;private static String url = null;private static String user = null;private static String password = null;static {try {Properties properties = new Properties();/*** 获取src路径下的文件的方式-->ClassLoader 类加载器*/properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/jdbc.properties"));driver = properties.getProperty("driver");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");Class.forName(driver);} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}public static Connection getConnection() {try {Connection connection = DriverManager.getConnection(url,user,password);return connection;} catch (SQLException e) {e.printStackTrace();return null;}}public static void close(Connection connection, Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}public static void close(Connection connection, Statement statement, ResultSet resultSet){close(connection,statement);if(resultSet!=null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}
}

仔细观察,提高了代码的复用性,通过配置文件修改数据库的属性,是不是值得我们学习呢?

案例思考

上面的代码,还是很不错的,但是也面临着一个问题,sql注入问题
纳尼?不可思议,观察sql语句,对于聪明的你我就不做更多的解释了?如何解决?PreparedStatement,之前我们说过,它是一个执行预编译sql的对象,如何操作呢?
首先原先的步骤得先发生下改变

  1. 导入驱动jar包
  2. 注册驱动
  3. 获取数据库连接对象 Connection
  4. 定义sql语句,注:sql的参数使用?来代替
  5. 获取执行sql语句的对象PreparedStatement
  6. 给占位符?赋值
  7. 执行sql语句,接收返回结果
  8. 处理结果
  9. 释放资源
    于是,我们有个加强版
    public static void main(String[] args) {String username = null;String password = null;Scanner in = new Scanner(System.in);Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");BufferedWriter bw = null;Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {ClassLoader classLoader = DemoPlus.class.getClassLoader();URL resource = classLoader.getResource("conf/log.txt");String path = resource.getPath();FileWriter fw = new FileWriter(path, true);bw = new BufferedWriter(fw);/*  FileOutputStream fos = new FileOutputStream(path,true);OutputStreamWriter osw = new OutputStreamWriter(fos);BufferedWriter bw = new BufferedWriter(osw);*/System.out.println("用户名:");username = in.nextLine();System.out.println("密码:");password = in.nextLine();connection = JDBCUtils.getConnection();String sql = "select * from tbl_user where username = ? and password = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1,username);preparedStatement.setString(2,password);resultSet = preparedStatement.executeQuery();String format = sdf.format(date);bw.newLine();bw.write("时间:" + format);bw.newLine();bw.write("用户名:" + username);bw.newLine();bw.write("密码:" + password);bw.newLine();if (resultSet.next()) {System.out.println("登录成功!");bw.write("登录成功!");} else {System.out.println("登录失败!");bw.write("登录失败!");}bw.newLine();bw.write("--------------------------------------");} catch (FileNotFoundException e) {System.out.println("未发现指定文件");System.exit(-1);} catch (SQLException e) {System.out.println("获取statement对象异常");System.exit(-1);} catch (IOException e) {System.out.println("IO读写异常");System.exit(-1);} finally {JDBCUtils.close(connection, preparedStatement, resultSet);try {bw.close();} catch (IOException e) {e.printStackTrace();}}}

我们再来sql注入一次,没门。。。

事务管理及演示

什么是事务?

指一个包含多个步骤的业务操作,如果这个业务被事务管理,则这多个步骤要么同时执行成功,要么同时失败。

模拟:转账

构建数据库表

我们的需求是张三给李四转1000元,期望结果张三余额是4000元,李四余额是6000元。

    public static void main(String[] args) {//获取数据库连接对象Connection connection = JDBCUtils.getConnection();try {//开启事务connection.setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}//定义sql语句String sql = "update tbl_account set balance = balance + ? where id = ? ";//获取执行预编译sql语句的PreparedStatement对象PreparedStatement preparedStatement1 = null;PreparedStatement preparedStatement2 = null;try {preparedStatement1 = connection.prepareStatement(sql);preparedStatement1.setDouble(1,-1000);preparedStatement1.setInt(2,1);preparedStatement2 = connection.prepareStatement(sql);preparedStatement2.setDouble(1,+1000);preparedStatement2.setInt(2,2);int count1 = preparedStatement1.executeUpdate();//此处发生错误int a = 3/0;int count2 = preparedStatement2.executeUpdate();//提交事务connection.commit();if(count1>0 && count2>0){System.out.println("转账成功!");}else{System.out.println("转账失败!");}} catch (SQLException e) {try {//事务回滚connection.rollback();} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace();}finally {JDBCUtils.close(connection,preparedStatement1);JDBCUtils.close(null,preparedStatement2);}}

以上代码,感兴趣慢慢研究,如果去掉了事务管理,会怎么样呢?

数据库连接池

概念

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

剖析

Java官方提供了DataSource接口,该接口由数据库厂商实现,用于构建数据库连接池,可以使用其方法来过去连接对象或者归还连接

C3P0数据库连接池

使用步骤

  1. 导入jar包
  2. 编写配置文件(注:文件必须是c3p0.properties或c3p0-config.xml)
  3. 通过ComboPooledDataSource 创建DataSource对象
  4. 获取连接Connection对象
  5. 编写sql语句,执行并处理结果
  6. 归还连接对象

配置文件

<c3p0-config><default-config><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property><property name="user">root</property><property name="password">mysql</property><!-- 初始化申请的连接数量 --><property name="initialPoolSize">10</property><!-- 最大的连接数量 --><property name="maxPoolSize">10</property><!-- 超时时间 --><property name="checkoutTimeout">3000</property></default-config><!-- This app is massive! --><named-config name="intergalactoApp"><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql://localhost/mybatis</property><property name="user">root</property><property name="password">mysql</property><!-- 初始化申请的连接数量 --><property name="initialPoolSize">10</property><!-- 最大的连接数量 --><property name="maxPoolSize">10</property><!-- 超时时间 --><property name="checkoutTimeout">3000</property></named-config>
</c3p0-config>
public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.导入jar包//2.编写配置文件//3.通过ComboPooledDataSource创建DataSource对象DataSource dataSource = new ComboPooledDataSource();//4.获取连接对象connection = dataSource.getConnection();//5.编写sql语句,执行并处理结果String sql = "select * from tbl_account";preparedStatement = connection.prepareStatement(sql);resultSet = preparedStatement.executeQuery();while (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");double balance = resultSet.getDouble("balance");System.out.println(id + " : " + name + " : " + balance);}} catch (SQLException e) {e.printStackTrace();} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {//6.归还连接对象connection.close();} catch (SQLException e) {e.printStackTrace();}}}}

Druid数据库连接池

使用步骤

  1. 导入jar包
  2. 编写配置文件(注:不同于c3p0,该配置文件可自定义名称及位置,通过properties来读取它)
  3. 通过DruidDataSourceFactory 创建DataSource对象
  4. 获取连接Connection对象
  5. 编写sql语句,执行并处理结果
  6. 归还连接对象

配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=mysql
maxActive=10
maxWait=3000

public class JDBCUtils {private static DataSource dataSource = null;static {try {Properties properties = new Properties();properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("conf/druid.properties"));dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}}public static DataSource getDataSource(){return dataSource;}public static Connection getConnection() throws SQLException {return dataSource.getConnection();}public static void close(Connection connection, Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}public static void close(Connection connection, Statement statement, ResultSet resultSet){close(connection,statement);if(resultSet!=null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}
}

Spring JDBC

概念

Spring框架对JDBC的简单封装,简化JDBC的开发

常用方法

  • 创建JdbcTemplate的对象,参数为一个数据库连接池DataSource对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(JDBCUtils.getDataSource());
  • 使用update方法来实现增删改,返回影响的行数
	String sql = "update tbl_account set balance = ? where id = ?";int count = jdbcTemplate.update(sql, balance, id);
	String sql = "insert into tbl_account values(null,?,?)";int count = jdbcTemplate.update(sql, name, balance);
	String sql = "delete from tbl_account where id = ?";int count = jdbcTemplate.update(sql, id);
  • 使用queryForMap 方法来返回数据,注:该查询的数据集的长度只能是1,列名作为key,值为value
	String sql = "select * from tbl_account where balance = ?";Map<String, Object> map = jdbcTemplate.queryForMap(sql,balance);
  • 使用queryForList 方法来返回数据集,注:该方式是先将每条数据封装为Map集合,再装载到List集合中
	String sql = "select * from tbl_account";List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
  • 使用queryForObject 方法来返回指定数值,注:该方式一般用于聚合函数的查询
	String sql = "select count(*) from tbl_account";Long total = jdbcTemplate.queryForObject(sql, long.class);
  • 使用query 方法来返回指定对象的数值

**方式一:**自己写RowMapper接口的方法
**方式二:**使用BeanPropertyRowMapper实现类

	String sql = "select * from tbl_account";/*List<Account> list = jdbcTemplate.query(sql,new RowMapper<Account>(){@Overridepublic Account mapRow(ResultSet resultSet, int i) throws SQLException {Account account = new Account();int id = resultSet.getInt("id");String name = resultSet.getString("name");double balance = resultSet.getDouble("balance");account.setId(id);account.setName(name);account.setBalance(balance);return account;}});*/List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));