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
  1. package com.lmd.jdbc;
  2. //接口,面向接口编程,更换数据库不变,通用性
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.sql.Statement;
  8. //mysql做的实现
  9. import com.mysql.jdbc.Driver;
  10. public class JDBCDemo1 {
  11. public static void main(String[] args) throws SQLException{
  12. //1.注册数据库驱动
  13. DriverManager.registerDriver(new Driver());
  14. //Class.forName("com.mysql.jdbc.Driver");
  15. //2.获取数据库连接
  16. String url = "jdbc:mysql://localhost:3306/day10";
  17. Connection con = DriverManager.getConnection(url, "root", "666666");
  18. //Connection con = DriverManager.getConnection(url +
  19. //"?user=root&password=666666");

  20. //Connection con = DriverManager.getConnection("jdbc:mysql:///
  21. //day10?user=root&password=666666");
  22. //3.获取传输器对象
  23. Statement st = con.createStatement();
  24. //4.利用传输器传输sql语句到数据库中执行,获取结果集对象
  25. ResultSet rs = st.executeQuery("select * from user");
  26. //5.遍历结果集获取查询结果
  27. while(rs.next()){
  28. String name = rs.getString("name");
  29. System.out.println(name);
  30. }
  31. //6.关闭资源
  32. rs.close();
  33. st.close();
  34. con.close();
  35. }
  36. }

      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代码。
  1. package com.mysql.jdbc;
  2. import java.sql.SQLException;
  3. public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  4. // Register ourselves with the DriverManager
  5. static {
  6. try {
  7. java.sql.DriverManager.registerDriver(new Driver());
  8. } catch (SQLException E) {
  9. throw new RuntimeException("Can't register driver!");
  10. }
  11. }
  12. public Driver() throws SQLException {
  13. // Required for Class.forName().newInstance()
  14. }
  15. }

             (2)、推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
           采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
         同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。
      3、数据库URL
         URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
  1. 常用数据库URL地址的写法:
  2. Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
  3. SqlServerjdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
  4. MySqljdbc:mysql://localhost:3306/sid
  5. Mysqlurl地址的简写形式: jdbc:mysql:///sid
  6. 常用属性:useUnicode=true&characterEncoding=UTF-8

      4、程序详解—Connection
         Jdbc程序中的Connection,它用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
  1. ResultSet createStatement():创建向数据库发送sqlstatement对象。
  2. PrepareStatement prepareStatement(sql) :创建向数据库发送预编译sqlPrepareSatement对象。
  3. CallableStatement prepareCall(sql):创建执行存储过程的callableStatement对象。
  4. void setAutoCommit(boolean autoCommit):设置事务是否自动提交。
  5. void commit() :在链接上提交事务。
  6. void rollback() :在此链接上回滚事务。
      
      5、程序详解—Statement
         Jdbc程序中的Connection,它用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
  1. ResultSet executeQuery(String sql) :用于向数据发送查询语句。
  2. int executeUpdate(String sql):用于向数据库发送insertupdatedelete语句
  3. boolean execute(String sql):用于向数据库发送任意sql语句
  4. void addBatch(String sql) :把多条sql语句放到一个批处理中。
  5. int[] executeBatch():向数据库发送一批sql语句执行。

      6、程序详解—ResultSet
        (1)、 Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
        (2)、ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
  1. 获取任意类型的数据
  2. getObject(int index)
  3. getObject(string columnName)
  4. 获取指定类型的数据,例如:
  5. getString(int index)
  6. getString(String columnName)
 
 
  1. ResultSet还提供了对结果集进行滚动的方法:
  2. boolean next():移动到下一行
  3. boolean previous():移动到前一行
  4. boolean absolute(int row):移动到指定行
  5. void beforeFirst():移动resultSet的最前面。
  6. void afterLast() :移动到resultSet的最后面。
例如:
  1. 表中:zs lisi wangwu
  2. rs.next(); rs.next(); 打印:lisi
  3. rs.previous();//zs
  4. rs.absolute(3);//wangwu 第三行
  5. String name = rs.getString("name");
  6. 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;  变成垃圾对象被回收。完整的如下:
  1. public class JDBCDemo1 {
  2. public static void main(String[] args){
  3. Connection conn = null;
  4. Statement stat = null;
  5. ResultSet rs = null;
  6. try {
  7. Class.forName("com.mysql.jdbc.Driver");
  8. String url = "jdbc:mysql://localhost:3306/day10";
  9. conn = DriverManager.getConnection(url, "root", "666666");
  10. stat = conn.createStatement();
  11. rs = stat.executeQuery("select * from user");
  12. while(rs.next()){
  13. String name = rs.getString("name");
  14. System.out.println(name);
  15. }
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. } finally {
  19. if (rs != null) {
  20. try {
  21. rs.close();
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }finally {
  25. rs = null;
  26. }
  27. }
  28. if (stat != null) {
  29. try {
  30. stat.close();
  31. } catch (SQLException e) {
  32. e.printStackTrace();
  33. }finally {
  34. stat = null;
  35. }
  36. }
  37. if (conn != null) {
  38. try {
  39. conn.close();
  40. } catch (SQLException e) {
  41. e.printStackTrace();
  42. }finally {
  43. conn = null;
  44. }
  45. }
  46. }
  47. }
  48. }
  (三)、JDBC增删改查
  1. create database day10 character set utf8 collate utf8_general_ci;
  2. use day10;
  3. create table user(
  4. id int primary key auto_increment,
  5. name varchar(40),
  6. password varchar(40),
  7. email varchar(60),
  8. birthday date
  9. )character set utf8 collate utf8_general_ci;
  10. insert into user(name,password,email,birthday) values('zs','123456','zs@sina.com','1980-12-04');
  11. insert into user(name,password,email,birthday) values('lisi','123456','lisi@sina.com','1981-12-04');
  12. insert into user(name,password,email,birthday) values('wangwu','123456','wangwu@sina.com','1979-12-04');
  1. mysql> select * from user;
  2. +----+--------+----------+-----------------+------------+
  3. | id | name | password | email | birthday |
  4. +----+--------+----------+-----------------+------------+
  5. | 1 | zs | 123456 | zs@sina.com | 1980-12-04 |
  6. | 2 | lisi | 123456 | lisi@sina.com | 1981-12-04 |
  7. | 3 | wangwu | 123456 | wangwu@sina.com | 1979-12-04 |
  8. +----+--------+----------+-----------------+------------+
  1. package com.lmd.util;
  2. import java.io.FileNotFoundException/FileReader;
  3. import java.sql.Connection/DriverManager/ResultSet;
  4. import java.sql.SQLException/Statement;
  5. import java.util.Properties;
  6. public class JDBCUtils {
  7. private static Properties prop = null;
  8. private JDBCUtils(){ }
  9. //只解析一次,放在静态代码块里
  10. static{
  11. prop = new Properties();
  12. try {
  13. prop.load(new FileReader(JDBCUtils.class.getClassLoader().
  14. getResource("config.properties").getPath()));
  15. } catch (FileNotFoundException e) {
  16. e.printStackTrace();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. throw new RuntimeException(e);
  20. }
  21. }
  22. /**
  23. * 获取连接
  24. * @return
  25. * @throws ClassNotFoundException
  26. * @throws SQLException
  27. */
  28. public static Connection getConn() throws ClassNotFoundException, SQLException{
  29. //1、注册数据库驱动
  30. Class.forName(prop.getProperty("driver"));
  31. //2、获取连接
  32. return DriverManager.getConnection(prop.getProperty("url"),
  33. prop.getProperty("username"), prop.getProperty("password"));
  34. }
  35. /**
  36. * 关闭连接
  37. * @param rs
  38. * @param stat
  39. * @param conn
  40. */
  41. public static void close(ResultSet rs, Statement stat, Connection conn) {
  42. if (rs != null) {
  43. try {
  44. rs.close();
  45. } catch (SQLException e) {
  46. e.printStackTrace();
  47. }finally {
  48. rs = null;
  49. }
  50. }
  51. if (stat != null) {
  52. try {
  53. stat.close();
  54. } catch (SQLException e) {
  55. e.printStackTrace();
  56. }finally {
  57. stat = null;
  58. }
  59. }
  60. if (conn != null) {
  61. try {
  62. conn.close();
  63. } catch (SQLException e) {
  64. e.printStackTrace();
  65. }finally {
  66. conn = null;
  67. }
  68. }
  69. }
  70. }
  1. 配置文件:config.properties 在src下
  2. driver=com.mysql.jdbc.Driver
  3. url=jdbc:mysql://localhost:3306/day10
  4. username=root
  5. password=66666

      1、使用executeUpdate(String sql)方法完成数据添加操作,示例操作:
  1. @Test
  2. public void add(){
  3. Connection conn = null;
  4. Statement stat = null;
  5. try {
  6. //1、注册数据库驱动;2、获取数据库连接
  7. conn = JDBCUtils.getConn();
  8. //3、获取传输器对象
  9. stat = conn.createStatement();
  10. //4、利用传输器传输sql语句到数据库中执行,获取结果集对象
  11. int count = stat.executeUpdate("insert into user values(null,'lily','666666'
  12. ,'lily8@163.com','1990-12-22')");
  13. //5、处理结果
  14. if (count>0) {
  15. System.out.println("执行成功!影响到的行数为"+count);
  16. } else {
  17. System.out.println("执行失败!");
  18. }
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. } finally {
  22. //6、关闭连接
  23. JDBCUtils.close(null, stat, conn);
  24. }
  25. }
结果如下:
 
      2、使用executeUpdate(String sql)方法完成数据修改操作,示例操作:
  1. public void update(){
  2. Connection conn = null;
  3. Statement stat = null;
  4. try {
  5. conn = JDBCUtils.getConn();
  6. stat = conn.createStatement();
  7. stat.executeUpdate("update user set password='888888' where id=2");
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. //6、关闭连接
  12. JDBCUtils.close(null, stat, conn);
  13. }
  14. }
结果如下:
 
      3、使用executeQuery(String sql)方法完成数据查询操作,示例操作:
  1. @Test
  2. public void find(){
  3. Connection conn = null;
  4. Statement stat = null;
  5. ResultSet rs = null;
  6. try {
  7. conn = JDBCUtils.getConn();
  8. stat = conn.createStatement();
  9. rs = stat.executeQuery("select * from user where name='lily'");
  10. while (rs.next()) {
  11. String name = rs.getString("name");
  12. String password = rs.getString("password");
  13. System.out.println(name+":"+password);
  14. }
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } finally {
  18. //6、关闭连接
  19. JDBCUtils.close(null, stat, conn);
  20. }
  21. }
结果如下:

      4、使用 executeUpdate(String sql)方法完成数据删除操作,示例操作:
  1. @Test
  2. public void delete(){
  3. Connection conn = null;
  4. Statement stat = null;
  5. try {
  6. conn = JDBCUtils.getConn();
  7. stat = conn.createStatement();
  8. stat.executeUpdate("delete from user where name='lily'");
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. } finally {
  12. //6、关闭连接
  13. JDBCUtils.close(null, stat, conn);
  14. }
  15. }
结果如下:
   (四)、案例:改写前面讲解的用户注册和登陆案例,实现如下需求:
  1. 1、把xml换成数据库,重写XmlUserDao
  2. 2、定义DAO接口,并定义Dao工厂,实现service层和dao层的解耦。
  3. 3、自定义dao异常。
  4. 4、防范sql注入攻击
       
  1. 一、为什么:要分层
  2. 使软件具有结构性,便于开发、维护和管理。
  3. 将不同功能模块独立,在需要替换某一模块时不需要改动其他模块,方便代码的复用、替换
  4. 二、层与层耦合的概念,利用工厂类解耦
  5. 在分层结构中,我们希望将各个功能
  6. 约束在各自的模块(层)当中的,而当属于某一层的对象、方法“入侵”到了其他层,如将web层的ServletContext对象
  7. 传入service层,或service层调用XMLDao独有的方法,就会导致层与层之间的关系过于“紧密”,当需要修改某一层时不可
  8. 避免的要修改其他关联的层,这和我们软件分层最初的设想-----层与层分离,一个层尽量不依赖其他层存在,
  9. 当修改一层时无需修改另一层的设想是违背的。这种“入侵”造成的“紧密”关系就早做层与层之间发生的“耦合”,
  10. 而去掉这种耦合性的过程就叫做层与层之间“解耦”
  11. 利用工厂类可以实现解耦的功能
  12. 三、如何判断一项功能到底属于哪一层
  13. 某一项功能属于哪一层,往往是不能明确确定出来的,这时可以参考如下标准进行判断:
  14. 1、此项功能在业务逻辑上更贴近与哪一层,放在哪一层更能较少耦合
  15. 2、此项功能是否必须使用某一层特有的对象
  16. 3、如果放在哪一层都可以,那么放在哪一层更方便技术上的实现,及方便代码的编写和维护
  17. 四、异常的处理
  18. 1、如果一个异常抛给上一层会增加程序的耦合性,请当场解决:如将xml解析错误抛给service层,那么当换成mysqldao时,
  19. 还需要修改service去掉xml解析异常的处理。
  20. 2、如果上一层明确需要此异常进行代码的流转,请抛出:如当查找一个用户信息而用户找不到时,
  21. 可以抛出一个用户找不到异常,明确要求上一层处理。
  22. 3、如果这一层和上一层都能解决尽量在这一层解决掉。
  23. 4、如果这一层不能解决,而上一层能解决抛给上一层。
  24. 5、如果所有层都不能解决,则应抛出给虚拟机使线程停止,但是如果直接抛出这个异常,则还需要调用者一级一级继续
  25. 往上抛出最后才能抛给虚拟机,所以还不如在出现异常的位置直接try...catch住后转换为RuntimeException抛出。
  26. :如读取配置文件出错,任何层都不能解决,转为RuntimeException抛出,停止线程。

1、改写案例
(1)、把xml换成数据库,重写XmlUserDao
  1. create table users(
  2. id int primary key auto_increment,
  3. username varchar(20),
  4. password varchar(50),
  5. nickname varchar(40),
  6. email varchar(50)
  7. );
  8. insert into users values(null,'admin','admin','admin','admin8@qq.com');
  (2)、重写XmlUserDao,改为MysqlUserDao.java。
1)、将XmlUserDao中的方法定义到一个接口中,注意:
  使用接口定义使用的三个方法;接口一定要有注释,并且是文档注释。
方法注释一方都放在接口中,而非实现类中。
在接口中使用接口注释后,当鼠标移到实现类中方法上就会有提示。
  1. package com.lmd.dao;
  2. import com.lmd.domain.User;
  3. public interface UserDao {
  4. /**
  5. * 根据用户名查找用户
  6. * @param username 用户名
  7. * @return 根据用户名找到的用户信息bean,若没找到返回null
  8. */
  9. public User findUserByUserName(String username);
  10. /**
  11. * 添加用户
  12. * @param user 要添加的用户信息bean
  13. */
  14. public void addUser(User user);
  15. /**
  16. * 根据用户名和密码查找对应的用户
  17. * @param username 用户名
  18. * @param password 密码
  19. * @return 找到的用户,若没找到返回null
  20. */
  21. public User findUserByUNandPSW(String username, String password);
  22. }
2)、仍需要前面设置的config.properties和JDBCUtils.java,开发MysqlUserDao.java:
               修改User.java,增加id;修改UserService.java一句话。
  1. //private XmlUserDao dao = new XmlUserDao();
  2. private MysqlUserDao dao = new MysqlUserDao();
这个切换数据库会改变代码,不太好,后面会讲怎么办? 这是一种耦合,后面会利用工厂类实现解耦
  1. package com.lmd.dao;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.Statement;
  5. import com.lmd.domain.User;
  6. import com.lmd.util.JDBCUtils;
  7. public class MysqlUserDao implements UserDao{
  8. @Override
  9. public User findUserByUserName(String username) {
  10. String sql = "select * from users where username='"+username+"'";
  11. Connection conn = null;
  12. Statement stat = null;
  13. ResultSet rs = null;
  14. try {
  15. conn = JDBCUtils.getConn();
  16. stat = conn.createStatement();
  17. rs = stat.executeQuery(sql);
  18. if (rs.next()) {
  19. User user = new User();
  20. user.setId(rs.getInt("id"));
  21. user.setUsername(rs.getString("username"));
  22. user.setPassword(rs.getString("password"));
  23. user.setNickname(rs.getString("nickname"));
  24. user.setEmail(rs.getString("email"));
  25. return user;
  26. } else {
  27. return null;
  28. }
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. throw new RuntimeException(e);
  32. } finally {
  33. JDBCUtils.close(rs, stat, conn);
  34. }
  35. }
  36. @Override
  37. public void addUser(User user) {
  38. String sql = "insert into users values (null,'"+user.getUsername()+
  39. "','"+user.getPassword()+"','"+user.getNickname()+"','"+user.getEmail()+"')";
  40. Connection conn = null;
  41. Statement stat = null;
  42. try {
  43. conn = JDBCUtils.getConn();
  44. stat = conn.createStatement();
  45. stat.executeUpdate(sql);
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. throw new RuntimeException(e);
  49. } finally {
  50. JDBCUtils.close(null, stat, conn);
  51. }
  52. }
  53. @Override
  54. public User findUserByUNandPSW(String username, String password) {
  55. String sql = "select * from users where username='"+username+"' and password='"+password+"'";
  56. Connection conn = null;
  57. Statement stat = null;
  58. ResultSet rs = null;
  59. try {
  60. conn = JDBCUtils.getConn();
  61. stat = conn.createStatement();
  62. rs = stat.executeQuery(sql);
  63. if (rs.next()) {
  64. User user = new User();
  65. user.setId(rs.getInt("id"));
  66. user.setUsername(rs.getString("username"));
  67. user.setPassword(rs.getString("password"));
  68. user.setNickname(rs.getString("nickname"));
  69. user.setEmail(rs.getString("email"));
  70. return user;
  71. } else {
  72. return null;
  73. }
  74. } catch (Exception e) {
  75. e.printStackTrace();
  76. throw new RuntimeException(e);
  77. } finally {
  78. JDBCUtils.close(rs, stat, conn);
  79. }
  80. }
  81. }

         2、定义DAO接口,并定义Dao工厂,实现service层和dao层的解耦。
  1. package com.lmd.factory;
  2. import java.io.FileReader;
  3. import java.util.Properties;
  4. import com.lmd.dao.UserDao;
  5. /**
  6. * 单例模式:保证在整个应用程序的生命周期中,
  7. * 任何一个时刻,单例类的实例只存在一个(也可以不存在)
  8. * @author angel11288
  9. *
  10. */
  11. public class DaoFactory {
  12. private static DaoFactory factory = new DaoFactory();
  13. //配置文件只读一次
  14. private static Properties prop = null;
  15. static{
  16. try {
  17. prop = new Properties();
  18. prop.load(new FileReader(DaoFactory.class.getClassLoader().
  19. getResource("config.properties").getPath()));
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. throw new RuntimeException(e);
  23. }
  24. }
  25. private DaoFactory() {}
  26. public static DaoFactory getFactory() {
  27. return factory;
  28. }
  29. public UserDao getDao() {
  30. try {
  31. String cla = prop.getProperty("UserDao");//com.lmd.dao.MysqlUserDao
  32. return (UserDao)Class.forName(cla).newInstance();
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. throw new RuntimeException(e);
  36. }
  37. }
  38. }
修改UserService:
  1. //private XmlUserDao dao = new XmlUserDao();
  2. //private MysqlUserDao dao = new MysqlUserDao();
  3. private UserDao dao = DaoFactory.getFactory().getDao();
配置文件config.properties:
  1. driver=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://localhost:3306/day10
  3. username=root
  4. password=666666
  5. UserDao=com.lmd.dao.MysqlUserDao
  6. #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注入的问题
  1. package com.lmd.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.PreparedStatement;
  5. import com.lmd.util.JDBCUtils;
  6. public class JDBCDemo3 {
  7. public static void main(String[] args) {
  8. Connection conn = null;
  9. PreparedStatement ps = null;
  10. ResultSet rs = null;
  11. try {
  12. conn = JDBCUtils.getConn();
  13. //主干语句
  14. ps = conn.prepareStatement("select * from user where name=? and password=?");
  15. ps.setString(1, "lisi");
  16. ps.setString(2, "888888");
  17. rs = ps.executeQuery();
  18. while (rs.next()) {
  19. System.out.println(rs.getString("email"));
  20. }
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. } finally {
  24. JDBCUtils.close(rs, ps, conn);
  25. }
  26. }
  27. }

(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类型,可调用如下方法设置:
  1. PreparedStatement.setCharacterStream(index, reader, length);
  2. //注意length长度须设置,并且设置为int型
  3. //当包过大时修改配置:[mysqld] max_allowed_packet=64M

         (2)、对MySQL中的Text类型,可调用如下方法获取:
  1. reader = resultSet. getCharacterStream(i);
  2. 等价于
  3. reader = resultSet.getClob(i).getCharacterStream()
  1. package com.lmd.job;
  2. /*
  3. * create table textdemo(
  4. * id int primary key auto_increment,
  5. * name varchar(20),
  6. * content MEDIUMTEXT
  7. * );
  8. */
  9. import java.io.File/FileReader;
  10. import java.sql.Connection/PreparedStatement/ResultSet;
  11. import org.junit.Test;
  12. import com.lmd.util.JDBCUtils;
  13. public class TextDemo1 {
  14. @Test
  15. public void addText() {
  16. Connection conn = null;
  17. PreparedStatement ps = null;
  18. ResultSet rs = null;
  19. try{
  20. conn = JDBCUtils.getConn();
  21. ps = conn.prepareStatement("insert into textdemo values (null, ?,?)");
  22. ps.setString(1, "三生三世十里桃花.txt");
  23. File file = new File("三生三世十里桃花.txt");
  24. ps.setCharacterStream(2, new FileReader(file), file.length());
  25. ps.executeUpdate();
  26. } catch(Exception e){
  27. e.printStackTrace();
  28. } finally {
  29. JDBCUtils.close(rs, ps, conn);
  30. }
  31. }
  32. }
修改java虚拟机启动内存大小:
查看安装目录下的myeclipse.ini:
  1. #utf8 (do not remove)
  2. -startup
  3. plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
  4. --launcher.library
  5. plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.300.v20150602-1417
  6. -vm
  7. D:Program FilesMyEclipse2016inarycom.sun.java.jdk8.win32.x86_64_1.8.0.u66injavaw.exe
  8. -configuration
  9. D:Program FilesMyEclipse2016configuration
  10. -install
  11. D:Program FilesMyEclipse2016
  12. -vmargs
  13. -Xmx1024m
  14. -XX:ReservedCodeCacheSize=64m
  15. -Dosgi.nls.warnings=ignore
异常处理
  1. 1.Exception in thread "main" java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setCharacterStream
  2. (ILjava/io/Reader;J)Vps.setCharacterStream(2, new FileReader(file), file.length());
  3. 第三个参数是long型的是从1.6才开始支持的,驱动里还没有开始支持。
  4. 解决方案:ps.setCharacterStream(2, new FileReader(file), (int)file.length());
  5. 2.Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  6. 文件大小过大,导致PreparedStatement中数据多大占用内存,内存溢出
  7. -Xms256M-Xmx256M
  8. 3.com.mysql.jdbc.PacketTooBigException: Packet for query is too large (10886466 > 1048576).
  9. You can change this value on the server by setting the max_allowed_packet' variable.
  10. 数据库连接传输用的包不够大,传输大文本时报此错误
  11. 在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:ProgramDataMySQLMySQL Server 5.6下的my.ini:在[msqld]中写
  1. public void findText() {
  2. Connection conn = null;
  3. PreparedStatement ps = null;
  4. ResultSet rs = null;
  5. try{
  6. conn = JDBCUtils.getConn();
  7. ps = conn.prepareStatement("select * from textdemo where id=?");
  8. ps.setInt(1,2);
  9. rs = ps.executeQuery();
  10. while (rs.next()) {
  11. String name = rs.getString("name");
  12. System.out.println(name);//三生三世十里桃花.txt
  13. Reader reader = rs.getCharacterStream("content");
  14. Writer writer = new FileWriter(name);
  15. char[] chs = new char[1024];
  16. int len = 0;
  17. while ((len = reader.read(chs))!=-1) {
  18. writer.write(chs, 0, len);
  19. }
  20. reader.close();
  21. writer.close();
  22. }
  23. } catch(Exception e){
  24. e.printStackTrace();
  25. } finally {
  26. JDBCUtils.close(rs, ps, conn);
  27. }
  28. }

 3、使用JDBC处理二进制数据:
         (1)、对于MySQL中的BLOB类型,可调用如下方法设置:
  1. PreparedStatement. setBinaryStream(i, inputStream, length);

         (2)、对MySQL中的BLOB类型,可调用如下方法获取:
  1. InputStream in = resultSet.getBinaryStream(i);
  2. InputStream in = resultSet.getBlob(i).getBinaryStream();

  1. @Test
  2. public void addBlob() {
  3. try{
  4. conn = JDBCUtils.getConn();
  5. ps = conn.prepareStatement("insert into blobdemo values (null,?,?)");
  6. ps.setString(1, "欢迎您.mp3");
  7. File file = new File("1.mp3");
  8. ps.setBinaryStream(2, new FileInputStream(file), file.length());
  9. ps.executeUpdate();
  10. }
  11. }
  12. @Test
  13. public void findBlob() {
  14. try{
  15. conn = JDBCUtils.getConn();
  16. ps = conn.prepareStatement("select * from blobdemo where id=?");
  17. ps.setInt(1,1);
  18. rs = ps.executeQuery();
  19. while (rs.next()) {
  20. String name = rs.getString("name");
  21. System.out.println(name);//欢迎您.mp3
  22. InputStream input = rs.getBinaryStream("content");
  23. OutputStream output = new FileOutputStream(name);
  24. byte[] bys = new byte[1024];
  25. int len = 0;
  26. while ((len = input.read(bys))!=-1) {
  27. output.write(bys, 0, len);
  28. }
  29. input.close();
  30. output.close();
  31. }
  32. }
但是:图片和音乐等一般不会存在数据库中,只需要知道就行。大文件一般放在服务器的硬盘上,访问的是服务器存在的
文件的硬盘路径,通过读路径得到大文件。

 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语句。
  1. package com.lmd.batch;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.Statement;
  5. import com.lmd.util.JDBCUtils;
  6. public class StatementBatch {
  7. public static void main(String[] args) {
  8. Connection conn = null;
  9. Statement stat = null;
  10. try {
  11. conn = JDBCUtils.getConn();
  12. stat = conn.createStatement();
  13. stat.addBatch("create database day10batch");
  14. stat.addBatch("use day10batch");
  15. stat.addBatch("create table batchdemo("
  16. +"id int primary key auto_increment,"
  17. + "name varchar(20))");
  18. stat.addBatch("insert into batchdemo values(null,'aaa')");
  19. stat.addBatch("insert into batchdemo values(null,'bbb')");
  20. stat.addBatch("insert into batchdemo values(null,'ccc')");
  21. stat.addBatch("insert into batchdemo values(null,'ddd')");
  22. stat.executeBatch();//返回int类型的数组
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. } finally {
  26. JDBCUtils.close(null, stat, conn);
  27. }
  28. }
  29. }
结果如下:
  1. mysql> use day10batch;
  2. Database changed
  3. mysql> select * from batchdemo;
  4. +----+------+
  5. | id | name |
  6. +----+------+
  7. | 1 | aaa |
  8. | 2 | bbb |
  9. | 3 | ccc |
  10. | 4 | ddd |
  11. +----+------+
  12. 4 rows in set
  13. mysql>


        (3)、实现批处理有两种方式,第一种方式: PreparedStatement.addBatch()
               采用PreparedStatement.addBatch()实现批处理,将一组参数添加到此 PreparedStatement 对象的批处理命令中。
                   优点:发送的是预编译后的SQL语句,执行效率高。
                   缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
  1. package com.lmd.batch;
  2. import java.sql.Connection;
  3. import java.sql.PreparedStatement;
  4. import com.lmd.util.JDBCUtils;
  5. /*
  6. create table psbatch(
  7. id int primary key auto_increment,
  8. name varchar(20)
  9. );
  10. */
  11. public class PreparedStatementBatch {
  12. public static void main(String[] args) {
  13. Connection conn = null;
  14. PreparedStatement ps = null;
  15. try {
  16. conn = JDBCUtils.getConn();
  17. ps = conn.prepareStatement("insert into psbatch values(null,?)");
  18. for (int i = 0; i < 100001; i++) {
  19. ps.setString(1, "name"+i);
  20. ps.addBatch();
  21. if (i%1000==0) {
  22. ps.executeBatch();
  23. ps.clearBatch();
  24. }
  25. }
  26. ps.executeBatch();//为了剩下的那一条记录
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. } finally {
  30. JDBCUtils.close(null, ps, conn);
  31. }
  32. }
  33. }
结果如下:
  1. mysql> select count(*) from psbatch;
  2. +----------+
  3. | count(*) |
  4. +----------+
  5. | 419 |
  6. +----------+
  7. 1 row in set
  8. mysql> select count(*) from psbatch;
  9. +----------+
  10. | count(*) |
  11. +----------+
  12. | 689 |
  13. +----------+
  14. 1 row in set
  15. mysql>



原文地址:https://www.cnblogs.com/angel11288/p/28ea9cd81e4e2665d1a778f7e14e16d5.html