人生最糟的不是失去爱的人,而是因为太爱一个人,而失去了自己。
之前操作mysql数据库:
1)使用mysql客户端工具
2)使用客户端连接mysql服务器
3)发送sql语句到mysql服务器,执行
使用java程序发送sql语句到数据库服务器端执行,这叫用到了JDBC技术!!!!
jdbc是Oracle-Sun公司设计的一套专门用于java程序操作数据库的接口。
连接mysql数据库:
数据库主机
端口
数据库用户名
数据库密码
连接的数据库
|-Driver接口: 数据库驱动程序的接口,所有具体数据库厂商需要的驱动程序需要实现次接口。
Connection connect(String url, Properties info) 用于获取数据库连接
|-Connection接口:与具体的数据库的连接对象。
Statement createStatement() 创建一个静态sql语句对象
PreparedStatement prepareStatement(String sql) 创建预编译的sql语句对象
CallableStatement prepareCall(String sql) 创建存储过程的sql语句对象
|-Statement接口:用于执行静态 SQL 语句
int executeUpdate(String sql) 执行更新操作的sql语句 (create/alter/drop/insert/update/delete)
ResultSet executeQuery(String sql) 执行查询操作的sql语句
(select)
|- PreparedStatement接口:用于执行预编译的 SQL 语句(是Statement的子接口)
int executeUpdate() 执行更新操作的sql语句
ResultSet executeQuery() 执行查询操作的sql语句
|- CallableStatement接口:用于执行 SQL 存储过程的接口(是PreparedStatement的子 接口)
ResultSet executeQuery() 执行存储过程的sql语句
|- ResultSet接口:结果集对象。 存储所有数据库查询的结果,用该对象进行数据遍历。
boolean next() : 把光标移动到下一行。如果下一行有数据,返回true,如果没有下一行数 据,返回false。
getXXX(列索引|列字段名称): 获取字段的数据
/** * 通过jdbc执行DDL语句 * @author APPle * */ public class Demo1 { //数据库的连接的URL private static String url = "jdbc:mysql://localhost:3306/day17"; //数据库用户名 private static String user = "root"; //数据库密码 private static String password = "root";
public static void main(String[] args){ Connection conn = null; Statement stmt = null; try { //1.驱动驱动程序 Class.forName("com.mysql.jdbc.Driver"); //2.从驱动程序管理类获取连接 conn = DriverManager.getConnection(url, user, password); //3.通过Connection对象获取Statement对象 stmt = conn.createStatement(); //4.准备sql语句 String sql = "CREATE TABLE student(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),gender VARCHAR(2))"; //5.执行sql语句,返回结果 int count = stmt.executeUpdate(sql);
System.out.println("影响了"+count+"行"); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ //6.关闭资源(先关闭statement,再关闭connection) if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } } |
/** * 通过jdbc执行DML语句(insert/update/delete) * @author APPle * */ public class Demo2 { //数据库的连接的URL private static String url = "jdbc:mysql://localhost:3306/day17"; //数据库用户名 private static String user = "root"; //数据库密码 private static String password = "root"; /** * 执行插入操作 */ @Test public void test1(){ Connection conn = null; Statement stmt = null; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection(url, user, password); //创建Statment对象 stmt = conn.createStatement(); //准备sql String sql = "INSERT INTO student(NAME,gender) VALUES('张三','男')"; //执行sql,返回结果 int count = stmt.executeUpdate(sql); System.out.println("影响了"+count+"行"); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ //关闭资源 if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } }
/** * 执行更新操作 */ @Test public void test2(){ Connection conn = null; Statement stmt = null; //声明外部变量 String name = "陈六"; int id=2; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection(url, user, password); //创建Statment对象 stmt = conn.createStatement(); //准备sql String sql = "UPDATE student SET NAME='"+name+"' WHERE id="+id+""; //变量和String拼接sql //执行sql,返回结果 int count = stmt.executeUpdate(sql); System.out.println("影响了"+count+"行"); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ //关闭资源 if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } }
/** * 执行删除操作 */ @Test public void test3(){ Connection conn = null; Statement stmt = null; //声明外部变量 int id=2; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection(url, user, password); //创建Statment对象 stmt = conn.createStatement(); //准备sql String sql = "DELETE FROM student WHERE id="+id+""; //变量和String拼接sql //执行sql,返回结果 int count = stmt.executeUpdate(sql); System.out.println("影响了"+count+"行"); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ //关闭资源 if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } } |
/** * 使用jdbc执行DQL语句(select) * @author APPle * */ public class Demo3 { //数据库的连接的URL private static String url = "jdbc:mysql://localhost:3306/day17"; //数据库用户名 private static String user = "root"; //数据库密码 private static String password = "root";
public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接 conn = DriverManager.getConnection(url, user, password); //创建Statement对象 stmt = conn.createStatement(); //准备sql String sql = "SELECT * FROM student";
rs = stmt.executeQuery(sql);
//移动光标到下一行 //rs.next(); /** * 注意: * 1)如果光标在第一行之前,使用rs.getXX()获取列值,报错:Before start of result set * 2)如果光标在最后一行之后,使用rs.getXX()获取列值,报错:After end of result set */
//获取列值 /*if(rs.next()){ //使用列索引
int id = rs.getInt(1); String name = rs.getString(2); String gender = rs.getString(3);
//使用列名称 int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t"+name+"\t"+gender+"\t"); }*/
//迭代结果集 while(rs.next()){ //使用列索引 /* int id = rs.getInt(1); String name = rs.getString(2); String gender = rs.getString(3); */ //使用列名称 int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t"+name+"\t"+gender+"\t"); }
} catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ if(rs!=null) try { rs.close(); } catch (SQLException e1) { e1.printStackTrace(); throw new RuntimeException(e1); } //关闭资源 if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } }
} } |
/** * 使用PreparedStatement执行sql语句 * @author APPle * */ public class Demo1 {
/** * 插入操作 */ @Test public void test1(){ Connection conn = null; PreparedStatement stmt = null; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "INSERT INTO student(NAME,gender) VALUES(?,?)"; //预编译sql:使用?号代替参数值。一个?号代表一个参数值 //创建PreparedStatement对象,执行预编译的sql语句 stmt = conn.prepareStatement(sql); //设置参数 /** * 参数一: 参数位置。从1开始 * 参数二: 参数实际值 * 注意: 所有参数必须要赋值 */ stmt.setString(1, "rose"); stmt.setString(2, "女"); //发送参数,执行sql语句 int count = stmt.executeUpdate(); System.out.println(count); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, null); } }
/** * 修改操作 */ @Test public void test2(){ Connection conn = null; PreparedStatement stmt = null; //声明变量 String name = "jacky"; int id = 8; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "UPDATE student SET NAME=? WHERE id=?"; //预编译sql:使用?号代替参数值。一个?号代表一个参数值 //创建PreparedStatement对象,执行预编译的sql语句 stmt = conn.prepareStatement(sql); //设置参数 stmt.setString(1,name); stmt.setInt(2, id); //发送参数,执行sql语句 int count = stmt.executeUpdate(); System.out.println(count); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, null); } }
/** * 删除操作 */ @Test public void test3(){ Connection conn = null; PreparedStatement stmt = null; //声明变量 int id = 8; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "DELETE FROM student WHERE id=?"; //预编译sql:使用?号代替参数值。一个?号代表一个参数值 //创建PreparedStatement对象,执行预编译的sql语句 stmt = conn.prepareStatement(sql); //设置参数 //任何类型都可以使用setObject进行赋值 stmt.setObject(1, id); //发送参数,执行sql语句 int count = stmt.executeUpdate(); System.out.println(count); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, null); } }
/** * 查询操作 */ @Test public void test4(){ Connection conn = null; PreparedStatement stmt = null; //声明变量 String name = "张%"; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "SELECT * FROM student WHERE NAME LIKE ?"; //创建PreparedStatement,预编译sql语句 stmt = conn.prepareStatement(sql); //设置参数 stmt.setObject(1, name); //发送参数,执行sql,返回结果集 ResultSet rs = stmt.executeQuery(); //遍历结果集 while(rs.next()){ int id = rs.getInt("id"); String nameStr = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t"+nameStr+"\t"+gender+"\t"); } }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, null); } }
} |
==============Statement和PreparedStatement的区别==========================================
一、语法结构不同
1)Statment执行静态sql语句,且sql可以拼接。
2)PreparedStatement可以先执行预编译的sql语句,在预编译sql语句中使用?进行参数占位,后面 在进行参数赋值
二、原理不同
1)Statement不能进行sql缓存
2)而PreparedStatement可以进行sql缓存,执行效率会比Statement快!!!
三、安全性不同
1)Statement存在sql注入的风险
2)而PreparedStatement可以有效防止用户注入。
/** * 执行带有输入参数存储过程 */ @Test public void test1(){ Connection conn = null; CallableStatement stmt = null; ResultSet rs = null; try{ //获取连接 conn = JdbcUtil.getConnection(); //创建CallableStatement对象 String sql = "CALL pro_findById(?)";//预编译sql、可以带?号 //执行预编译的sql stmt = conn.prepareCall(sql); //设置参数 stmt.setInt(1, 4); //发送参数,执行sql,返回结果 rs = stmt.executeQuery();// 注意: 执行存储过程必须使用exeuteQuery!!!! //遍历结果 while(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t"+name+"\t"+gender+"\t"); } }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, rs); } } |
/** * 执行带有输出参数存储过程 */ @Test public void test2(){ Connection conn = null; CallableStatement stmt = null; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "CALL pro_findById2(?,?)"; // 第一个参数时输入参数,第二个参数是输出参数 //创建CallableStatement对象 stmt = conn.prepareCall(sql); //设置输入参数 stmt.setObject(1, 4); //注册一个输出参数 /** * 参数一: 参数位置 * 参数二: 表示存储过程中的OUT参数的数据库类型 */ stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
//发送参数,执行存储过程 stmt.executeQuery();
/** * 如何获取存储过程的返回值:OUT参数值。使用getXXX方法 */ String name = stmt.getString(2);//和预编译语句中的参数位置保持一致!!!! System.out.println("结果:"+name); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); }finally{ //关闭资源 JdbcUtil.close(conn, stmt, null); } } |
/** * 使用类路径的方式加载db.properties文件 */ //1.得到字节码对象 Class clazz = JdbcUtil.class; //2.通过方法加载文件,使用类路径方式 // / : 表示类路径的根目录(放class字节码文件目录) // java项目: bin目录下 // web项目: WEB-INF/classes目录下 InputStream in = clazz.getResourceAsStream("/db.properties"); // 从类路径的根目录下开始找 //InputStream in = clazz.getResourceAsStream("db.properties"); // 文件和当前类处于同一个目录 prop.load(in); |
Class.getResourceAsStream("/db.properties");
Class.getResourceAsStream("db.properties");
这里的 斜杠 / 表示当前项目的类路径的根目录。
当前项目是java项目,/ 在项目的bin目录下
当前项目是web项目,/在项目的WEB-INF/classes目录下
注意: 无论是java项目还是web项目,在开发时如果把资源文件放在src下,那么src下的文件都会拷贝到类路径的根目录下。
回顾重点内容:
mysql加强:
数据约束:
默认值: default 默认值
唯一: unique
非空: not null
主键:primary key (唯一+非空 )
自增长: auto_increment
外键: foreign key
级联操作:
级联更新: on update cascade
级联删除: on delete cascade
多表查询:
1)内连接查询: inner join
作用: 当两种表满足了连接条件时的数据才会显示。
2)左外连接: left outer join
作用:左表完全显示。右表的数据如果满足了连接条件,则显示对应的数据,如果不满 足条件,那么显示null。
3)右外连接: right outer join
作用:右表完全显示。左表的数据如果满足了连接条件,则显示对应的数据,如果不满 足条件,那么显示null。
数据库设计:
第一范式:要求表的每个字段都必须独立的不可分割的最小单元。
第二范式:要求表的除主键外的其他字段都和主键有依赖关系。(一张表表达一个意思)
第三范式:要求表的除主键外的其他字段都只能由主键决定。
今天目标: jdbc基础
使用java程序访问(操作)数据库(发送sql语句),这叫用到了jdbc技术!!!!
1)先登录到数据库:
数据库的主机地址(ip地址)
端口
数据库用户名
数据库用户密码
连接的数据库
2)发送sql语句
|-Driver接口: 驱动程序接口。
|-Connection connect() 用于连接数据库的方法
可以使用驱动程序管理类获取连接:
DriverManager.getConnection(url,user,pasword);
|-Connection接口: 代表和数据库的连接
|- Statement createStatement() 创建Statement接口的对象。
|- PreparedStatement prepareStatement(String sql) 创建PreparedStatement接口的对象。
|- CallableStatement prepareCall(String sql)创建CallableStatement接口的对象。
|-Statement接口:用于执行静态 SQL 语句。
|- int executeUpdate(String sql) 执行DDL和DML语句(更新sql语句)
|- ResultSet executeQuery(String sql) 执行DQL语句(查询sql语句)
|-PreparedStatement接口:用于执行预编译的 SQL 语句
|- int executeUpdate():执行DDL和DML语句(更新sql语句)
|- ResultSet executeQuery() 执行DQL语句(查询sql语句)
|-CallableStatement接口: 用于执行存储过程的SQL语句
|- ResultSet executeQuery() 存储过程只能执行查询sql
|-ResultSet接口: 表示数据库结果集。
|- boolean next() 将光标移至下一行
|- getXXX(): 获取结果集中的每列的值
1)注册驱动程序
2)获取连接对象
3)准备sql语句(DDL+DML)
4)创建Statement对象( Statement,PreparedStatment,CallableStement )
5)执行sql语句
DDL+DML: executeUpdte(sql)
DQL; executeQuery()
6)返回结果,处理结果
ResultSet
7)关闭资源
Statement vs PreparedStatement
语法不同:
1)Statement只能执行静态的sql语句
2)PreparedStatement即可以执行静态sql语句,也可以执行预编译sql语句
安全性不同:
1)Statement可以被用户进行sql注入
2)PreparedStatement不能被用户注入sql,比Statement更安全!!
执行效率问题:
1)Statement不能利用数据库sql缓存功能
2)PreparedStatement可以利用数据库sql缓存功能。比Statement的执行效率更高的!!
结论:建议尽量使用PreparedStatment。
使用类路径读取jdbc.propertiers文件
总结:
1)jdbc:使用java程序操作数据库的技术。(一套接口)
2)jdbc步骤:
2.1 注册驱动
Class.forName(驱动类名称)
2.2 获取连接
Connection conn = DriverManger.getConnection(url,user,password);
2.3 创建Statement/PreparedStatement/CallableStament
conn.createStatement()
conn.preparedStaement(sql);
conn.preparedCall(sql)
2.4 如果使用PreparedStatement/CallableStament,设置参数
stmt.setXXX(参数位置,参数值)
2.5 执行sql/发送参数
stmt.executeUpdate() DDL+DML
ResultSet rs = stmt.executeQuery() DQL
2.6 处理结果集
rs.next()
rs.getXXX() 列索引和列名称
2.7 关闭资源
rs.close();
stmt.close();
conn.close();
之前:一次操作只能发送一条sql语句到数据库服务器,效率并不高!
如果要插入2000条记录,那么必须发送2000条sql语句。
如果IO流的话,一次写出一个字节,显然效率效率并不高,所以可以使用缓存字节数组提高每 次写出的效率。
现在:插入2000条记录,但现在使用sql缓存区,一次发送多条sql到数据库服务器执行。这种做法就叫做批处理。
Statement批处理:
void addBatch(String sql) 添加sql到缓存区(暂时不发送)
int[] executeBatch() 执行批处理命令。 发送所有缓存区的sql
void clearBatch() 清空sql缓存区
PreparedStatement批处理:
void addBatch() 添加参数到缓存区
int[] executeBatch() 执行批处理命令。 发送所有缓存区的sql
void clearBatch() 清空sql缓存区
/** * 获取自增长值 * @author APPle * */ public class Demo1 { @Test public void test(){ //插入部门表数据 String deptSql = "INSERT INTO dept(deptName) VALUES(?)"; //插入员工表数据 String empSql = "INSERT INTO employee(empName,deptId) VALUES(?,?)"; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ //获取连接 conn = JdbcUtil.getConnection();
/** * 1)插入部门 */ //预编译部门sql //stmt = conn.prepareStatement(deptSql); // /** * 1.1 使用两个参数的prepareStatement()方法,指定可以返回自动增长的键值 * Statement.RETURN_GENERATED_KEYS: 可以返回自动增长值 * Statement.NO_GENERATED_KEYS: 不能返回自动增长值 */ stmt = conn.prepareStatement(deptSql, Statement.RETURN_GENERATED_KEYS); //设置参数 stmt.setString(1, "秘书部"); //执行部门sql stmt.executeUpdate(); /** * 1.2 获取自增长的值 */ rs = stmt.getGeneratedKeys(); Integer deptId = null; if(rs.next()){ deptId = rs.getInt(1);//得到第一行第一列的值 }
/** * 2)插入员工,员工在刚添加的部门中 */ stmt = conn.prepareStatement(empSql); //设置参数 stmt.setString(1, "李四"); stmt.setInt(2, deptId); //如何获取刚刚添加的部门ID?? //执行员工sql stmt.executeUpdate();
}catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } } |
mysql:
字符串: varchar char 65535
大文本数据: tinytext , longtext ,text
字节: bit
大字节文件: tinyblob(255byte), blob(64kb),MEDIUMBLOB(约16M) longblob(4GB)
oracle:
字符串: varchar2 char 65535
大文本数据: clob
字节: bit
大字节文件: blob
/** * 对大文本数据处理 * @author APPle * */ public class Demo1 {
/** * 文件保存到数据中 */ @Test public void testWrite(){ Connection conn = null; PreparedStatement stmt = null; try{ //获取连接 conn = JdbcUtil.getConnection(); //创建PreparedStatement String sql = "INSERT INTO test1(content) VALUES(?)"; stmt =conn.prepareStatement(sql); //设置参数 /** * 参数一: 参数位置 * 参数二: 输入字符流 */ /** * 读取本地文件,返回输入字符流 */ FileReader reader = new FileReader(new File("e:/Demo1.java")); stmt.setClob(1, reader); //执行sql int count = stmt.executeUpdate(); System.out.println("影响了"+count+"行"); }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, null); } }
/** * 从数据中读取文本内容 */ @Test public void testRead(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "SELECT * FROM test1 where id=?"; stmt = conn.prepareStatement(sql); //设置参数 stmt.setInt(1, 2); //执行sql,返回结果集 rs = stmt.executeQuery(); if(rs.next()){ //方式一:当做字符串取出数据 /* String content = rs.getString("content"); System.out.println(content); */
//方式二:返回输入流形式 Clob clob = rs.getClob("content"); Reader reader = clob.getCharacterStream(); //写出到文件中 FileWriter writer = new FileWriter(new File("e:/Demo2.java")); char[] buf = new char[1024]; int len = 0; while( (len=reader.read(buf))!=-1){ writer.write(buf, 0, len); } //关闭流 writer.close(); reader.close(); }
}catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } } |
/** * 对字节文件处理 * @author APPle * */ public class Demo2 {
/** * 文件保存到数据库中 */ @Test public void testWrite(){ Connection conn = null; PreparedStatement stmt = null; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "insert into test2(content) values(?)"; stmt = conn.prepareStatement(sql); //设置参数 /** * 参数一:参数位置 * 参数二:输入字节流 */ /** * 读取本地文件 */ InputStream in = new FileInputStream(new File("e:/abc.wmv")); //stmt.setBlob(1, in); stmt.setBinaryStream(1, in); //执行 stmt.executeUpdate(); }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, null); } }
/** * 注意: mysql数据库默认情况下,只能存储不超过1m的文件,由于max_allowed_packet变量的限制 * 可以修改: %mysql%/my.ini文件, 修改或添加max_allowed_packet变量,然后重启mysql即可!! */
/** * 从数据中读取字节内容 */ @Test public void testRead(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ //获取连接 conn = JdbcUtil.getConnection(); String sql = "SELECT * FROM test2 where id=?"; //获取PreparedStatement stmt = conn.prepareStatement(sql); //设置参数 stmt.setInt(1, 1); //执行sql rs = stmt.executeQuery(); if(rs.next()){ //返回输入流 //InputStream in = rs.getBinaryStream("content"); InputStream in = rs.getBlob("content").getBinaryStream(); //写出文件中 FileOutputStream out = new FileOutputStream(new File("e://3.jpg")); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ out.write(buf, 0, len); } //关闭流 out.close(); in.close(); } }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } } |
所谓的事务,如果把多条sql语句看做一个事务,那么这个事务要么一起成功,要么一起失败!!
set autocommit =0 / 1; 设置是否自动提交事务
1: 表示自动提交事务,每执行一条sql语句,自动提交事务。
0: 表示关闭自动提交事务。
start transaction; 开启事务
commit; 提交事务,一旦提交事务不能回滚
rollback; 回滚事务。回滚到事务的起始点。
Connection.setAutoCommit(false) 开启事务
Connection.commit(); 成功执行,最后提交事务
Connection.rollback(); 一旦遇到错误,回滚事务
/** * 模拟银行转账 * @author APPle * */ public class Demo1 {
/** * 从eric的账户转2000元到rose的账户上 */ @Test public void testTransfer(){ //从eric账户上扣除2000元 String delSql = "UPDATE account SET BALANCE=BALANCE-2000 WHERE NAME='eric'"; //向rose账户打入2000元 String addSql = "UPDATE account SET BALANCE=BALANCE+2000 WHERE NAME='rose'"; Connection conn = null; PreparedStatement stmt = null; try{ //获取连接 conn = JdbcUtil.getConnection(); //开启事务 conn.setAutoCommit(false); // 等价于set autocommit=0;
//预编译delSQL stmt = conn.prepareStatement(delSql); //执行delSQL stmt.executeUpdate();
//发生异常 int i = 100/0;
//预编译addSql stmt = conn.prepareStatement(addSql); //执行addSql stmt.executeUpdate();
//提交事务 conn.commit();// 等价于commit;
}catch(Exception e){ e.printStackTrace(); //回滚事务 try { conn.rollback(); // 等价于rollback; } catch (SQLException e1) { e1.printStackTrace(); } }finally{ JdbcUtil.close(conn, stmt, null); } } } |
原子性: 要么一起成功过,要么一起失败
一致性: 数据库应该从一个一致性的状态到另一个一致性的状态,保持不变
隔离性: 多个并发事务直接应该可以相互隔离
脏读 不可重复读 幻读
read uncommitted: 否 否 否
read committed : 是 否 否
repeatable read: 是 是 否
serializable: 是 是 是
结论: 隔离性越高,数据库的性能越差。
持久性: 事务一旦提交,应该永久保持下来。
回顾重点内容
jdbc基础
jdbc开发步骤:
1)注册驱动程序:
Class.forName("com.mysql.jdbc.Driver");
2)从驱动管理类获取连接
Connection conn = DriverManager.getConnection(url,user,password);
3)通过连接对象可以创建Statement,PreparedStatement,CallableStatement
conn.createStatement(): 执行静态sql语句
conn.preparedStatement(sql): 执行预编译的sql语句(带?号的sql)
conn.preparedCall(sql): 执行存储过程调用sql语句
4)如果是预编译的sql,继续参数赋值
stmt.setXXX(1,参数值);
5)执行sql/发送参数
Statement.executeUpdate(sql); 执行更新sql
ResultSet rs =Statement.executeQuery(sql): 执行查询sql
PreparedStatement.executeUpdate() : 执行更新sql
ResultSet rs = PreparedStatement.executeQuery(); 执行查询sql
6)处理结果集
while(rs.next()){
rs.getXXX(索引值);
rs.getXXX(列名称)
}
7)关闭资源:
ResultSet.close();
Statement.close();
Connection.close();
之前:使用jdbc每次只能发送1条sql语句,如果同时执行2000次插入,向数据库服务器发送2000条 插入语句,这种做法的效率不高!
现在:使用jdbc的批处理执行2000次插入,但每次向数据库的服务器发送20条sql,一共只需要发送100次就可以了!那么这种的做法的效率会比之前的做法高!!!
批处理:一次发送多条SQL语句
Statement的批处理:
void addBatch(String sql) 把sql添加到缓存区中(没有发送的)
int[] executeBatch() 执行批处理缓存中sql语句(发送到数据库执行)
void clearBatch() 清空缓存区sql语句
PreparedStatement的批处理:
void addBatch() 把参数添加到缓存区中
int[] executeBatch() 执行批处理缓存中所有参数(发送到数据库执行)
void clearBatch() 清空缓存区参数列表
字符:
存储字符内容: mysql: char varchar 长度有限的。 65535
大容量的字符字段:
mysql: text longtext(4G字符内容)
oracle : clob longclob
字节:
mysql: blob(65kb) mediumblob(16mb) longblog(4GB)
oracle: blob
innoDB数据库类型: 支持事务。
MyISAM: 不支持事务,但执行效率高。
set autocommit=0;
commit;
rollback;
Connection.setAutoCommit(false); 开启事务
Connection .commit(); 提交事务
Connection.rollback(); 回滚事务
原子性:一个事务的操作要么一起成功,要么一起失败!
一致性: 在一个事务执行后,数据库从一个一致性的状态转变为另一个一致性。
隔离性: 多个事务并发过程中,希望能够相互隔离的。
多个事务在并发产生以下的现象:
脏读: 一个事务读到了另一个事务没有提交的数据
不可重复读:一个事务读到了另一个事务已经提交的更新数据(update)
幻读: 一个事务读到了另一个事务已经提交的插入数据(insert)
数据库可以设置不同的隔离级别来防止以上现象:(是否能够防止现象)
脏读 不可重复读 幻读
read uncommitted: 否 否 否
read committed: 是 否 否
repeatable-read : 是 是 否
serializable: 是 是 是
持久性: 事务一旦提交,应该永久保存下来
1)批处理
2)大容量数据(应用)
3)获取自增长的值(了解)
4)事务
4.1 什么是事务?
4.2 数据库事务如何使用?
4.3 jdbc中如何使用事务?
4.4 事务有哪些特性?
之前jdbc步骤: 获取连接 -> 得到Statement-> 发送sql -> 关闭连接
通讯录系统:
CURD 四次
问题: 获取连接对象需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,这样连接对象的使用效率并不高!!!
办法: 能不能让连接对象反复使用???如果可以的话,连接对象使用率提高!
这就可以用连接池实现!!
自定义连接池实现:
1)连接池中初始化若干个连接对象
2)当连接池中连接不足,重新连接数据库获取新的连接对象
3)总连接对象不能超过最大连接数
/** * 自定义的连接池 * @author APPle * */ public class MyPool { //连接参数 private static String url = "jdbc:mysql://localhost:3306/day19"; private static String user = "root"; private static String password = "root"; private static String driverClass = "com.mysql.jdbc.Driver";
//使用集合存储多个连接对象 LinkedList /** * 连接池的初始连接数 */ private int initSize = 5; /** * 连接池的最大连接数 */ private int maxSize = 8; /** * 连接池中的当前连接数,用于记录连接中有几个连接对象? */ private int currentSize = 0;
public MyPool(){ /** * 创建若干个连接对象,放入到集合中 */ for(int i=1;i<=initSize;i++){ pool.addLast(createConnection()); currentSize++; } }
static{ try { //注册驱动 Class.forName(driverClass); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException(e); } }
/** * 创建连接方法 */ private Connection createConnection(){ try { Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } }
/** * 对外提供获取连接的方法 */ public Connection getConnection(){ /** * 1)判断连接池中是否还有连接对象,池中有连接,直接取出池中连接 */ if(pool.size()>0){ /** * 直接返回连接池中的一个连接对象 */ return pool.removeFirst(); }
/** * 2)如果初始连接数不足的情况下,需要获取新的连接对象。但不能超过最大连接数 */ if(currentSize Connection conn = createConnection(); currentSize++; return conn; }
/** * 3)超过最大连接数时,需要处理(抛出异常或让用户等待) */ throw new RuntimeException("超过了最大连接数,请稍后再来"); }
/** * 对外提供释放连接的方法 */ public void releaseConnection(Connection conn){ /** * 把用完的连接对象放回到连接池中 */ pool.addLast(conn); }
} |
1)静态代理:
1.1 创建Connection接口的实现类(代理类)
1.2 在代理类中,重写close方法
注意: 使用静态代理,需要把原有对象的方法全部实现。
2)动态代理:
使用jdk自带的动态代理类
Proxy类:
static Object newProxyInstance( 用于创建代理类对象方法。
ClassLoader loader, 参数一:类加载器,可以使用当前项目的任意类获取类加载器
Class>[] interfaces, 参数二:代理类需要实现的接口
InvocationHandler h) 参数三: 指定代理类代理了之后要如何处理
InvocationHandler接口: 代理类的处理程序接口
Object invoke( 调用代理类对象的方法
Object proxy, 代理类对象实例
Method method, 当前正在调用哪个方法
Object[] args 当前调用方法时传入的参数
)
如果以上自定义连接要拿到实际应用中使用,还需要不断优化。
1)比如线程并发问题
2)达到最大连接数应该先等待,再抛出异常
3)当连接空闲超过一定时间,连接池要回收连接
....
所以这时就使用别人写的连接池工具!!
DBCP: DataBase Connection Pool
特点:
1)Apache旗下的软件,开源连接池
2)Tomcat的服务器的连接池默认实现
使用步骤:
1)导入dbcp的jar包
commons-dbcp-1.4.jar 核心包
commons-pool-1.5.6.jar 辅助包
2)创建连接池对象BasicDataSource对象
3)设置连接参数(url,user,password,dirverClass)
4)设置连接池参数(初始连接数,最大连接数,最大等待时间)
5)获取连接对象(getConnection() 方法)
@Test public void test1(){ try { //1.创建连接池对象 BasicDataSource ds = new BasicDataSource(); //2.设置连接参数 ds.setUrl("jdbc:mysql://localhost:3306/day19"); ds.setUsername("root"); ds.setPassword("root"); ds.setDriverClassName("com.mysql.jdbc.Driver");
//3.设置连接池参数 ds.setInitialSize(5);//初始连接数 ds.setMaxActive(8);//最大连接数 ds.setMaxWait(3000);//超过最大连接数时,最大等待时间 ds.setMaxIdle(3000);//最大空闲时间
//4.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn.hashCode()); if(i==5){ //释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池) conn.close(); } } } catch (SQLException e) { e.printStackTrace(); }
} |
使用配置文件加载
1)配置文件(dbcp.properties)
url=jdbc:mysql://localhost:3306/day19 username=root password=root driverClassName=com.mysql.jdbc.Driver
initialSize=5 maxActive=8 maxWait=3000 maxIdle=3000 |
2)程序
@Test public void test2(){ try { Properties prop = new Properties(); prop.load(Demo.class.getResourceAsStream("/dbcp.properties")); //1.创建连接池对象 BasicDataSource ds = (BasicDataSource)BasicDataSourceFactory.createDataSource(prop);
//2.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn.hashCode()); if(i==5){ //释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池) conn.close(); } } }catch (Exception e) { e.printStackTrace(); } } |
特点:
1)开源连接池
2)Hibernate框架,默认推荐使用C3P0作为连接池实现
使用步骤:
1)导入c3p0的jar包
c3p0-0.9.1.2.jar 核心包
2)创建连接池对象ComboPooledDataSource对象
3)设置连接参数(url,user,password,dirverClass)
4)设置连接池参数(初始连接数,最大连接数,最大等待时间)
5)获取连接对象(getConnection() 方法)
/** * 使用C3p0连接池 * @author APPle * 注意: DataSource接口是Sun公司设计的用于规范连接池实现的接口。 * */ public class Demo {
@Test public void test1(){ try { //1.创建连接池对象 ComboPooledDataSource ds = new ComboPooledDataSource(); //2.设置连接参数 ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/day19"); ds.setUser("root"); ds.setPassword("root");
//3.设置连接池参数 ds.setInitialPoolSize(5);// 初始化连接数 ds.setMaxPoolSize(8);//最大连接数 ds.setCheckoutTimeout(3000);//最大等待时间
//4.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn); if(i==5){ /** * 把连接对象放回连接池 */ conn.close(); } }
} catch (Exception e) { e.printStackTrace(); } }
/** * 使用配置文件方式读取参数信息 * 注意: c3p0会默认查询类路径的c3p0-config.xml文件,文件名不能错!! */ @Test public void test2(){ try { //1.创建连接池对象(方式一: 使用默认配置(default-config)) //ComboPooledDataSource ds = new ComboPooledDataSource();
//2.创建连接池对象(方式二: 使用命名配置(named-config:mysql_day18)) ComboPooledDataSource ds = new ComboPooledDataSource("mysql_day18");
//2.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn); if(i==5){ /** * 把连接对象放回连接池 */ conn.close(); } } } catch (SQLException e) { e.printStackTrace(); } } } |
练习: 把通讯录程序改造为使用C3p0连接池工具管理连接。
BeanUtils工具可以方便开发者对象javabean进行操作
JavaBean规范:
1)必须有无参的构造方法
2)把属性进行私有化
3)提供公开的getter和setter方法
使用步骤:
1)导入beanutills的jar包
commons-beanutils-1.8.3.jar 核心包
commons-logging-1.1.3.jar 辅助包
1)对JavaBean的属性进行赋值
/** * 1)对javabean的属性操作 */ @Test public void test1() throws Exception{ /* //1.创建javabean对象 Student student = new Student(); //2.赋值 student.setId(1); student.setName("eric"); student.setGender(true); student.setScore(79.43); student.setBirth(new Date()); */
//1.使用反射创建javabean对象 Object student = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.对javabean进行赋值 /** * 该方法是给avabean的一个属性赋值 * 参数一:需要赋值的javabean对象 * 参数二: 需要赋值的属性名称 * 参数三: 属性值 * 注意: * 1)如果基本数据类型(Integer,Double,Float,Long,Char,Boolean),可以由字符串类型自动转换过去 */ /** * 注册一个日期转换器 * 参数一: 注册的转换器对象 * 参数二: 转换后的目标类型 */ ConvertUtils.register(new DateLocaleConverter(), java.util.Date.class); BeanUtils.copyProperty(student, "name", "jacky");//给Student的name属性赋值 BeanUtils.copyProperty(student, "id", "2"); BeanUtils.copyProperty(student, "gender", "true"); BeanUtils.copyProperty(student, "score", 84.78); BeanUtils.copyProperty(student, "birth", "2014-10-01"); //默认情况下,不能把字符串的日期转换为Date类型
System.out.println(student); } |
2)对JavaBean的对象赋值
/** * 拷贝整个javabean的对象(所有属性一次性拷贝) * @throws Exception */ @Test public void test2() throws Exception{ Student student = new Student(); student.setId(1); student.setName("eric"); student.setGender(true); student.setScore(79.43); student.setBirth(new Date());
//1.使用反射构造对象 Object student2 = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.把student的属性拷贝到student2中 /** * 参数一: 目标的javabean * 参数二: 拷贝的源javabean */ BeanUtils.copyProperties(student2, student);
System.out.println(student2); } |
3)把Map数据赋值给JavaBean对象
/** * 把map集合的数据拷贝到javabean中 * @throws Exception */ @Test public void test3() throws Exception{ Map map = new HashMap(); map.put("id", 2); map.put("name", "rose"); map.put("gender", true); map.put("score", 90.32); map.put("birth", new Date());
//1.使用反射构造对象 Object student2 = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.把map数据拷贝到student2中 BeanUtils.copyProperties(student2, map);
System.out.println(student2); } |
在web项目中,经常需要拷贝请求中的参数到JavaBean对象中,这时可以使用BeanUtils工具,把请求的Map结构的所有参数一次拷贝到javaben中。
/** * 优化后的方法,请求参数拷贝到javabean对象中 * @param request * @param obj */ public static void copyRequestToBean(HttpServletRequest request,Object obj){ System.out.println("优化后的拷贝方法..."); //1.得到请求的所有参数 /** * map: 键是参数的名称,值是参数内容 */ Map requestMap = request.getParameterMap(); //2.把请求map对象拷贝到obj中 try { BeanUtils.copyProperties(obj, requestMap); }catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } |
希望知道以下:
一、数据库相关的信息:(封装到数据库元对象: DataBaseMetaData)
数据库的版本
驱动程序的版本
二、参数相关的信息:(封装到参数元对象:ParameterMetaData)
参数数量
参数类型
三、结果相关的信息:(封装到结果集元对象: ResultSetMetaData)
列数量
列名称
jdbc步骤:
1)连接数据库,获取Connection对象 (得到DataBaseMetaData)
2)创建Statment,预编译sql语句 (得到ParameterMetaData)
3)设置参数
4)执行sql,返回结果集 (得到ResultSetMetaData)
5)遍历结果集
6)关闭连接
练习:
抽取两个通用的jdbc方法
1)通用的修改方法(insert、update、delete)
update() 用于任何表的任何更新操作
2)通用的查询方法,查询后返回List集合
find() 无论查询什么表,都能返回我们需要的List集合
提示:使用元数据
/** * jdbc的工具方法 * @author APPle * */ public class DBUtil { private static ComboPooledDataSource ds = new ComboPooledDataSource();
/** * 通用的更新方法 * 不同更新操作的不同点: * 1)sql语句不同 * 2)参数列表不同 */ public static void update(String sql,Object[] values){ Connection conn = null; PreparedStatement stmt = null; try { //获取连接 conn = ds.getConnection(); //预编译sql stmt = conn.prepareStatement(sql); //设置参数 ParameterMetaData md = stmt.getParameterMetaData(); //得到参数数量 int count = md.getParameterCount(); if(values!=null){ for(int i=1;i<=count;i++){ stmt.setObject(i, values[i-1]); } } //执行sql stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } }
}
/** * 通用的查询方法 * 不同点: * 1)sql语句不同 * 2)参数不同 * 3)List集合中的对象不同 */ public static List find(String sql,Object[] values,Class clazz){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; List list = new ArrayList(); try { //获取连接 conn = ds.getConnection(); //预编译sql stmt = conn.prepareStatement(sql); //设置参数 ParameterMetaData md = stmt.getParameterMetaData(); int count = md.getParameterCount(); if(values!=null){ for(int i=1;i<=count;i++){ stmt.setObject(i, values[i-1]); } } //执行sql rs = stmt.executeQuery(); //得到结果集的元数据 ResultSetMetaData rsmd = rs.getMetaData(); //得到列数据 int columnCount = rsmd.getColumnCount(); //遍历结果集 while(rs.next()){ //创建一个javabean对象 Object obj = clazz.newInstance(); //把每行的结果放入javabean对象 //遍历每列 for(int i=1;i<=columnCount;i++){ //得到列名称 String columnName = rsmd.getColumnName(i); //得到列内容 Object value = rs.getObject(columnName); //把列内容赋值给javabean /** * 约定前提: 数据库表的字段名称和javabean的属性名称保持一致!!!! */ BeanUtils.copyProperty(obj, columnName, value); }
//把javabean放入list集合 list.add(obj); } return list; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } } |
DBUtils是Apache的工具,是一个对jdbc的简单封装的工具。提供了一些通用的jdbc操作方法。
1)导入jar包
commons-dbutils-1.2.jar
2)使用的API
QueryRunner类: 通过此类可以执行更新操作或者查询操作。
update(.....): 用于更新操作(DDL、DML)
query(.....): 用于查询操作(DQL)
ResultSetHandler接口:用于封装查询之后的结果。
Object handle(ResultSet rs) : 用于封装数据
常用的实现类:
ArrayHandler: 把结果集的第一行的数据封装成对象数组。
ArrayListHandler:把结果集的每一行数据封装对象数组,把这个对象数组放入List 中
BeanHandler: 把结果集的第一行数据封装成javabean
BeanListHandler: 把结果集的每一行数据封装成javabean,把这个javabean放入LIst 中
ScalarHandler: 把结果集的第一行第一列取出。通常用于聚合函数查询。例如 (count()/max())
如果表的字段名称和javabean的属性名称不一致时,需要自定义ResultSetHandler的实现类
/** * 使用dbutils工具 * @author APPle * */ public class Demo1 {
@Test public void testInsert() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(ds); //2.执行操作 //qr.update("INSERT INTO student(NAME,age,address) VALUES('张三11',20,'广州天河')"); qr.update("INSERT INTO student(NAME,age,address) VALUES(?,?,?)", new Object[]{"eric11",20,"广州天河"}); }
@Test public void testInsert2() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); Connection conn = ds.getConnection(); QueryRunner qr = new QueryRunner(); qr.update(conn,"INSERT INTO student(NAME,age,address) VALUES('张三22',20,'广州天河')"); //手动关闭连接 conn.close(); } } |
/** * dbutils执行查询操作 * @author APPle * */ public class Demo2 { /** * ArrayHandler: 把结果集的第一行的数据封装成对象数组。 */ @Test public void test1() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Object[] arr = (Object[])qr.query("select * from student where id=?", new ArrayHandler(),new Object[]{2}); for(Object obj:arr){ System.out.println(obj); } }
/** * ArrayListHandler: 把结果集的每一行数据封装对象数组,把这个对象数组放入List中 * @throws Exception */ @Test public void test2() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql List for(Object[] arr:list){//一行 //一列 for(Object obj:arr){ System.out.print(obj+"\t"); } System.out.println(); } }
/** * BeanHandler: 把结果集的第一行数据封装成javabean * 约定前提: 表的字段名称和javabean的属性名称保持一致!! */ @Test public void test3() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Student student = (Student)qr.query("select * from student", new BeanHandler(Student.class)); System.out.println(student); }
/** * BeanListHandler: 把结果集的每一行数据封装成javabean,把这个javabean放入LIst中 * 约定前提: 表的字段名称和javabean的属性名称保持一致!! */ @Test public void test4() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql List for (Student student : list) { System.out.println(student); } }
/** * ScalarHandler: 把结果集的第一行第一列取出。通常用于聚合函数查询。例如(count()/max()) */ @Test public void test5() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Long count = (Long)qr.query("select count(id) from student", new ScalarHandler(1)); System.out.println("行数: "+count); }
/** * 如果表的字段名称和javabean的属性名称不一致时,需要自定义ResultSetHandler的实现类 */ @Test public void test6() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); List for (Student student : list) { System.out.println(student); } } }
/** * 自定义ResultSetHandler */ class MyStudentHandler implements ResultSetHandler{
@Override public Object handle(ResultSet rs) throws SQLException { List while(rs.next()){ Student s = new Student(); s.setId(rs.getInt("sid")); s.setName(rs.getString("sname")); s.setAge(rs.getInt("sage")); s.setAddress(rs.getString("saddress")); list.add(s); } return list; }
} |
回顾重点内容
jdbc加强:
1)事务
1.1 mysql数据库事务
开启事务: set autocommit=0;
提交事务: commit;
回滚事务: rollback;
1.2 jdbc控制事务
开启事务: Connection.setAutoCommit(false);
提交事务: Connection.commit();
回滚事务: Connection.rollback();
1.3 事务的四个特性
原子性: 一个事务的所有操作要么一起成功,要么一起失败!
一致性: 一个事务执行后数据库应该从一个一致性的状态转变另一个一致性的状态。
隔离性: 多个事务并发操作应该要相互隔离。
如果不相互隔离,可能会产生以下现象:
脏读:不可取。一个事务读到了另一个事务未提交的更新数据
不可重复读:一个事务读到了另一个事务已经提交的更新数据。
幻读: 一个事务读到了另一个事务已经提交的插入数据。
事务存在四种隔离级别(是否防止):
脏读 不可重复读 幻读
read uncommited 否 否 否
read committed 是 否 否
repeatable-read 是 是 否
serialzable 是 是 是
持久性: 一个事务一旦提交。数据永久保存下来。
jdbc操作:
1)获取连接
2)创建Statement
3)执行sql语句(update, insert,delete select)
4) 得到结果集,处理结果集
5) 关闭资源
连接池:
1)Connection对象的利用率,提高执行sql的效率
2)控制java程序使用最大连接数,从而防止数据库奔溃
DBCP ( Datatabse Connection Pool)
是Apache组织的产品。
使用步骤:
1)导入包
commons-dbcp-1.4.jar 核心包
commons-pool-1.5.6.jar 辅助包
2)BasicDataSource连接池对象
C3P0
是开源框架(hibernate内置默认的连接池工具C3P0))
1)导入包
c3p0-0.9.1.2.jar 核心包
BeanUtils工具就是方便开发者操作javabean对象。
1)拷贝一个javabean对象的属性
2)从一个javabean拷贝到另一个javabean对象(所有属性)
3)从一个map集合中拷贝到javabean对象中。
1)导入包
commons-beanutils-1.8.3.jar 核心包
commons-logging-1.1.3.jar 辅助包
使用元数据,可以编写更通用的jdbc代码。
什么是元数据?
1)连接数据库
知道连接的哪个数据库,必须使用数据库的元数据对象(DatabaseMetaData)
2)预编译statement执行sql
insert into studnetx(xxxx) values(?,?);
预编译sql之后,知道预编译的sql有几个参数,必须使用参数元数据(ParameterMetaData)
3)执行查询sql,返回结果集
ResultSet rs,知道表的字段数,和字段名称,必须使用结果集的元数据(ResultSetMetaData)
简单的jdbc代码的封装
QeuryRunner类:
update() 更新
query() 查询
使用步骤
1)导入包
总结:
jdbc优化
1)连接池: 管理连接对象
DBCP
C3P0
2) BeanUtils工具: 拷贝javabean对象
3)元数据:为了明白使用元数据写出更加通用的jdbc代码
3)DBUtils: 对jdbc的简单封装
QueryRunner:
update()
query()
ReusltHandler接口:把结果集封装成不同的对象
BeanHandler
BeanListHandler
ScalarHandler
1)通用的更新方法(用于执行所有更新相关的操作)
update(String sql,Object[] params)
2) 通用的查询方法
List
场景1: 编写servlet,接收参数:
request.getParameter() /getParameterValues()
如果不做任何处理,就出现中文乱码问题。
解决编码问题: request.setCharacterEncoding("utf-8");
问题:如果在项目中的每一个servlet都加上request.setCharacterEncoding("utf-8");
显示代码重复啰嗦。能不能把这部分公共代码抽取处理,放在一个地方执行????
场景2: 登录 -> 输入信息 -> 登录成功 -> 看到用户主页(欢迎xxx回来。。。)
用于验证用户是否登录成功代码:
if(session==null){
跳转到登录页面
}else{
loginName = session.getAttribute("loginName");
if(loginName==null){
跳转到登录页面
}
}
-> 个人信息修改页面
-> 个人密码修改页面
如果用户不登录,直接访问用户主页,跳转到登录页面
在其他需要登录才能访问的页面中,同样也需要加上验证用户是否登录成功代码。
问题: 能不能把这部分公共验证用户是否登录成功代码抽取处理,在一个地方执行??
结论: 以上两种场景出现的问题,可以使用过滤器(Filter)解决!!!!
1)过滤器其实就是一个接口,Filter, javax.servet.Filter
2)过滤器就是一个对象,可以在请求一个资源(静态或动态资源),或响应一个资源,或请求和响应一个资源的时候,执行过滤任务!!!!
3)过滤器如何被执行?
过滤器也需要交给tomcat服务器运行!!!!
Servlet的三大组件:( 1)都需要交给web服务器运行 2)在web.xml文件中配置 )
Servlet接口
Filter接口
Listener接口
4)过滤器的生命周期
构造方法: 在web应用加载时创建过滤器对象。只执行一次。证明过滤器在web服务器中是单实例的
init方法: 在创建完过滤器对象之后被调用。只执行一次
doFilter方法: 执行过滤任务方法。执行多次。
destroy方法: web服务器停止或者web应用重新加载,销毁过滤器对象。
5)过滤器编写步骤:
5.1 编写一个java类,实现Filter接口,并实现其中的所有方法
5.1 在web.xml文件中配置Filter
<filter>
<filter-name>HelloFilterfilter-name>
<filter-class>gz.itcast.a_hello.HelloFilterfilter-class> filter>
<filter-mapping>
<filter-name>HelloFilterfilter-name>
<url-pattern>/hellourl-pattern> filter-mapping> |
5.3 把Filter部署到tomcat服务器运行!!!!
过滤器中的url-pattern: 表示的这个过滤器需要拦截的目标资源路径(可以servlet路径,也可以是静 态资源名称)
Servlet中的url-pattern: 表示访问这个servlet时的路径
url-pattern 浏览器访问目标资源的路径
精确过滤 /hello http://localhost:8080/day21/hello
/itcast/hello http://localhost:8080/day21/itcast/hello
模糊过滤 /* http://localhost:8080/day21/任意路径
/itcast/* http://localhost:8080/day21/itcast/任意路径
*.后缀名 http://localhost:8080/day21/任意路径.后缀名
注意:
1)url-pattern要么以斜杠开头,要么以*开头 例如: hello
2)不能同时使用两个模糊过滤。例如 /*.do 是非法的
3)如果存在多个需要被过滤的资源,可以写多个url-pattern去过滤。
4)如果是动态资源servlet,可以使用servlet的访问名称,也可以使用内部名称
<servlet-name>HelloServletservlet-name> |
5)过滤类型:
<dispatcher>REQUESTdispatcher> <dispatcher>FORWARDdispatcher> <dispatcher>INCLUDEdispatcher> <dispatcher>ERRORdispatcher> |
FilterConfig对象,过滤器配置对象,用于加载过滤器的参数配置
过滤器参数使用:
1)在web.xml文件中配置
<filter>
<filter-name>HelloFilterfilter-name>
<filter-class>gz.itcast.a_hello.HelloFilterfilter-class>
filter> |
2)在过滤器器中使用
/** * 2)init初始化方法 */ public void init(FilterConfig filterConfig) throws ServletException { System.out.println("2)Filter生命周期-init方法");
/** * 通过FilterConfig对象得到参数配置信息 */ //得到一个参数 System.out.println(filterConfig.getInitParameter("AAA"));
Enumeration //遍历所有参数 while(enums.hasMoreElements()){ String paramName = enums.nextElement(); String paramValue = filterConfig.getInitParameter(paramName); System.out.println(paramName+"="+paramValue); }
} |
doFilter(ServletRequest request, ServletResponse response, FilterChain chain): 执行过滤任务
参数一: ServletRequest是HttpServletRequest的父接口。实际上传入的是HttpServletRequest接口的实现类。
参数二: ServletResponse是HttpServletResponse的父接口。实际上传入HttpServletResponse接口的实现类。
参数三: FilterChain 过滤器链接口
doFilter(ServletRequest request, ServletResponse response):执行过滤器链中的下一个过滤器,如果没有下一个过滤器则执行目标资源。
*****过滤器链: 一个目标资源可以被多个过滤器过滤,那么形成一个过滤器链。***
注意:过滤器链中的过滤器执行顺序问题:由web.xml中filter-mapping的配置决定顺序。先配置的优先被执行。
23种java设计模式。单例模式,工厂模式,适配器模式,观察者模式,代理模式。。。。。。
装饰者模式:当开发者觉得某些类的某些方法不满足需要,向增强这些类的方法。这是就可以使用装饰者模式去装饰这些类。不满足需求的这些类,叫被装饰类。开发者需要重新编写装饰类去覆盖被装饰类。
BufferedReader:被装饰类。readLine方法不满足需要。
MyBufferReader: 装饰类。
装饰步骤:
1)继承被装饰类。BufferedReader。非final
2)装饰类中声明一个成员变量(被装饰类类型)
3)在构造方法,把被装饰类的实例接收到,赋值给成员变量
4)重写被装饰的方法。
为什么要进行网页内容压缩?
访问web服务器时,服务器会返回网页内容(数据)
用户访问一个页面:
100KB
100万个用户访问这个页面
1 ,000,000 * 100Kb = 100,000,000 = 服务器消耗了100G内容 (带宽)
用户访问一个页面:
100B
100万个用户访问这个页面
1 ,000,000 * 100B = 100,000,000 = 100M服务器消耗了100M内容 (带宽)
买服务器:
网络服务器运营商
按流量收费: 我们尽可能压缩网页内容,才输出给浏览器
怎么对网页内容压缩?
可以是java 的GZIPOutputStream类进行gzip压缩。
/** * 对网页内容进行压缩 */ //创建临时的字节数组容器 ByteArrayOutputStream byteArr = new ByteArrayOutputStream(); //创建GZIPOutputStream对象 GZIPOutputStream gzip = new GZIPOutputStream(byteArr); //开始写出压缩内容 gzip.write(sb.toString().getBytes()); //刷新缓冲区 gzip.finish();
//从临时的字节数组容器中得到压缩后的网页内容 byte[] result = byteArr.toByteArray();
System.out.println("压缩后的数据大小:"+result.length);
/** * 注意:告诉浏览器数据压缩格式 发送响应头:content-encoding:gzip */ response.setHeader("content-encoding", "gzip");
//把压缩后的内容输出到浏览器 response.getOutputStream().write(result); |
设计一个用于封装当前页所有分页相关的数据的对象,叫分页对象PageBean
/** * 分页对象。用于封装当前页的分页相关的所有数据 * @author APPle * */ public class PageBean { private List private Integer firstPage;//首页 private Integer prePage;//上一页 private Integer nextPage;//下一页 private Integer totalPage;//末页、总页数 private Integer currentPage;//当前页 private Integer totalCount;//总记录数 private Integer pageSize;//每页显示的记录数 } |
/** * 分页对象。用于封装当前页的分页相关的所有数据 * @author APPle * */ public class PageBean { private List private Integer firstPage;//首页 private Integer prePage;//上一页 private Integer nextPage;//下一页 private Integer totalPage;//末页、总页数 private Integer currentPage;//当前页 private Integer totalCount;//总记录数 private Integer pageSize;//每页显示的记录数 public List return data; } public void setData(List this.data = data; } public Integer getFirstPage() { return 1; } public void setFirstPage(Integer firstPage) { this.firstPage = firstPage; } /** * 计算上一页 * @return */ public Integer getPrePage() { return this.getCurrentPage()==this.getFirstPage() ? 1 : this.getCurrentPage()-1; } public void setPrePage(Integer prePage) { this.prePage = prePage; } /** * 计算下一页 * @return */ public Integer getNextPage() { return this.getCurrentPage()==this.getTotalPage()? this.getTotalPage() : this.getCurrentPage()+1; } public void setNextPage(Integer nextPage) { this.nextPage = nextPage; } public Integer getTotalPage() { return this.getTotalCount()%this.getPageSize()==0 ? this.getTotalCount()/this.getPageSize() :this.getTotalCount()/this.getPageSize()+1; } public void setTotalPage(Integer totalPage) { this.totalPage = totalPage; } public Integer getCurrentPage() { return currentPage; } public void setCurrentPage(Integer currentPage) { this.currentPage = currentPage; } public Integer getTotalCount() { return totalCount; } public void setTotalCount(Integer totalCount) { this.totalCount = totalCount; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; }
} |
/** * 员工对象 * @author APPle * */ public class Employee { private int id; private String name; private String gender; private int age; private String title; private String phone; private String email; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Employee(int id, String name, String gender, int age, String title, String phone, String email) { super(); this.id = id; this.name = name; this.gender = gender; this.age = age; this.title = title; this.phone = phone; this.email = email; } public Employee() { super(); // TODO Auto-generated constructor stub } @Override public String toString() { return "Employee [age=" + age + ", email=" + email + ", gender=" + gender + ", id=" + id + ", name=" + name + ", phone=" + phone + ", title=" + title + "]"; }
} |
/** * 员工的DAO类 * @author APPle * */ public class EmpDao {
/** * 提供一个查询当前页员工的方法 */ public List try { //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(JdbcUtil.getDataSource()); //2.执行查询sql操作 //计算查询的起始行 int startNo = (currentPage-1)*pageSize; List new BeanListHandler(Employee.class), new Object[]{startNo,pageSize}); return list; } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } }
/** * 提供查询总记录数的方法 * @param args */ public Integer queryTotalCount(){ try { //1.创建QueryRunner QueryRunner qr = new QueryRunner(JdbcUtil.getDataSource()); //2.执行sql查询 Long count = (Long)qr.query("SELECT COUNT(*) FROM employee", new ScalarHandler(1)); return count.intValue(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } |
/** * 员工的业务类 * @author APPle * */ public class EmpService {
/** * 提供用于封装PageBean对象方法(处理业务逻辑) */ public PageBean queryPageBean(Integer currentPage,Integer pageSize){ //封装PageBean分页对象数据 PageBean pageBean = new PageBean();
//设置当前页 pageBean.setCurrentPage(currentPage);
//设置每页显示的记录数 pageBean.setPageSize(pageSize);
EmpDao empDao = new EmpDao(); /** * 从数据库中查询出总记录数 */ int totalCount = empDao.queryTotalCount(); //设置总记录数 pageBean.setTotalCount(totalCount);
//设置当前页的数据 /** * 从数据库中查询出当前页的员工数据 */ List pageBean.setData(list);
return pageBean; } } |
public class PageServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /***********一、获取用户输入**************/ //设置当前页(获取用户的输入) String currentPageStr = request.getParameter("currentPage"); //如果用户没有输入,就是默认第1页 if(currentPageStr==null || currentPageStr.equals("")){ currentPageStr = "1"; }
//设置每页显示的记录数(获取用户的输入) String pageSizeStr = request.getParameter("pageSize"); if(pageSizeStr==null || pageSizeStr.equals("")){ pageSizeStr = "5"; }
/***************二、调用业务方法,获取PageBean对象***********************/ EmpService empService = new EmpService(); PageBean pageBean = empService.queryPageBean(Integer.parseInt(currentPageStr), Integer.parseInt(pageSizeStr));
/****************三、得到业务数据,跳转视图*********************/ //把PageBean数据发送到jsp页面中显示 request.setAttribute("pageBean", pageBean); //转发 request.getRequestDispatcher("/list.jsp").forward(request, response); } } |
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>分页显示效果title> head>
<body> <table border="1" align="center" width="700px"> <tr> <th>编号th> <th>姓名th> <th>性别th> <th>年龄th> <th>职位th> <th>电话th> <th>邮箱th> tr> <c:forEach items="${requestScope.pageBean.data}" var="emp"> <tr> <td>${emp.id }td> <td>${emp.name }td> <td>${emp.gender }td> <td>${emp.age}td> <td>${emp.title }td> <td>${emp.phone }td> <td>${emp.email }td> tr> c:forEach> <tr> <td colspan="7" align="center"> <%-- 1)如果当前页是首页,则不显示“首页”和“上一页”的连接 2)如果当前页是末页,则不显示“末页”和“下一页”的连接 --%> <c:choose> <c:when test="${pageBean.currentPage==pageBean.firstPage}"> 首页 上一页 c:when> <c:otherwise> <a href="${pageContext.request.contextPath }/PageServlet?currentPage=${pageBean.firstPage }&pageSize=${pageBean.pageSize}">首页a> <a href="${pageContext.request.contextPath }/PageServlet?currentPage=${pageBean.prePage }&pageSize=${pageBean.pageSize}">上一页a> c:otherwise> c:choose>
<c:choose> <c:when test="${pageBean.currentPage==pageBean.totalPage}"> 下一页 末页 c:when> <c:otherwise> <a href="${pageContext.request.contextPath }/PageServlet?currentPage=${pageBean.nextPage}&pageSize=${pageBean.pageSize}">下一页a> <a href="${pageContext.request.contextPath }/PageServlet?currentPage=${pageBean.totalPage }&pageSize=${pageBean.pageSize}">末页a> c:otherwise> c:choose> 当前第${pageBean.currentPage }页/共${pageBean.totalPage }页, 共${pageBean.totalCount }条 每页显示 <input type="text" name="pageSize" id="pageSize" size="2" value="${pageBean.pageSize }" onblur="changePageSize()"/> 条 td> tr> table>
<script type="text/javascript"> //改变每页显示记录数的方法 function changePageSize(){ //获取用户输入的记录数 var pageSize = document.getElementById("pageSize").value; //判断是否输入的数值 var reg = /^[1-9][0-9]?$/; if(!reg.test(pageSize)){ alert("请输入数组类型!"); return; } //把记录数发送到后台 var url = "${pageContext.request.contextPath}/PageServlet?pageSize="+pageSize; window.location.href=url; } script> body> html> |
根据用户的查询条件组装sql语句:
//2.组装sql StringBuffer sql = new StringBuffer("select * from department where 1=1 "); if(query!=null){ //部门名称不为空时 if(query.getDeptName()!=null && !query.getDeptName().equals("")){ sql.append(" and deptName like '%"+query.getDeptName()+"%'"); } //部门负责人不为空时 if(query.getPrincipal()!=null && !query.getPrincipal().equals("")){ sql.append(" and principal like '%"+query.getPrincipal()+"%'"); } //部门职能不为空时 if(query.getFunctional()!=null && !query.getFunctional().equals("")){ sql.append(" and functional like '%"+query.getFunctional()+"%'"); } } |
回顾重点内容
jdbc通用功能:
1)分页查询:
核心: 设计一个用于封装当前页分页相关数据的javabean对象
当前页的数据
首页
上一页
下一页
末页/总页数
当前页码
总记录数
每页显示记录数
三层结构:
1)dao层:
查询当前页的数据
总记录数
2)service层;
封装分页javabean对象
(
首页
上一页
下一页
末页/总页数
)
3)web层:
当前页码
每页显示记录数
2)条件查询
核心: 根据不同的条件拼凑条件查询的sql语句
条件查询sql语句:
select * from 表 where 1=1 (恒成立)
if 条件1
and 字段1 like 内容
if 条件2
and 字符2 like 内容
。。。。。
3)分页+条件查询
在分页的基础上,修改两条sql语句
3.1 查询当前页数据的sql语句
select * from 表 where 1=1
if 条件1
and 字段1 like 内容
if 条件2
and 字符2 like 内容
。。。。。
limit 起始行,每页查询行数;
3.2 查询总记录数的sql语句
select count(*) from 表 where 1=1
if 条件1
and 字段1 like 内容
if 条件2
and 字符2 like 内容
。。。。。;
今天的目标: 过滤器
场景1: 在servlet中获取用户参数数据 : request.getParameter("参数名") 遇到参数内容中文乱码问题,
post提交:
request.setCharacterEncoding("utf-8");
get提交:
手动解码: name = new String(name.getBytes("iso-8859-1"),"utf-8")
问题: 如何把这些重复的操作抽取出来呢?
这时就可以用到过滤器,把重复的操作代码写在过滤器中!!!
场景2:
登录页面 -> 输入用户民或密码 -》 后台检查是否成功 -》 登录成功,看到用户首页
( 用户名:xxxx )
把登录数据放在session域对象中(user)
用户首页如果不登录是看不到的:
判断用户登录权限代码:
HttpSession session = request.getSession(false);
if(session==null){
跳转到登陆页面
}else{
String user = (String)session.getAttribute("user");
if(user==null){
跳转到登录页面
}
}
用户资源修改页面(需要用户登录才能访问)
HttpSession session = request.getSession(false);
if(session==null){
跳转到登陆页面
}else{
String user = (String)session.getAttribute("user");
if(user==null){
跳转到登录页面
}
}
问题:如何把这些登录权限代码抽取出???
这是用到了过滤器,把这些登录权限代码写在过滤器中.
1)过滤器就是一个Filter接口,在javax.servlet.Filter;
2)过滤器是servlet的三大组件之一:
servlet的三大组件:
2.1 (servlet) Servlet接口: javax.servlet.Servlet; 作用:用于开发动态网页
2.2 (过滤器)Filter接口: javax.servlet.Filter; 作用:???
2.3 (监听器)Listener接口: javax.servlet.*
servlet组件的特点:
1)把组件配置到web.xml文件中
2)组件就可以交给tomcat服务器运行!!!!
3)作用:
过滤器的作用,就是一个实现了Filter接口的对象,这个对象可以在请求资源(可能是动态网页或者静态网页)时,或者在响应资源时,或者在请求和响应资源时,执行过滤任务。
构造方法: 创建过滤器对象的时候调用。在加载当前项目的时候加载过滤器,只调用1次。单 实例的多线程。
init方法: 在创建完过滤器对象之后调用。只调用1次。
doFilter方法: 过滤任务方法。会调用n次。每次访问目标资源的时候,doFilter就会被调用。
destory方法:在销毁过滤器对象的时候调用。在web项目重新部署或tomcat服务器停止的时候销毁过滤器对象。
|
注意: servlet的url-pattern指的是访问servlet的路径,而过滤器的url-pattern指的是需要过滤的路径。
而这个过滤的路径,可以是任意资源路径(可以是静态网页路径,页可以是动态网页路径)。
过滤器url-pattern 访问目标资源
精确过滤 /target http://localhsot:8080/day20/target
/index.html http://localhsot:8080/day20/index.html
模糊过滤 /* http://localhsot:8080/day20/任意路径
/itcast/* http://localhsot:8080/day20/itcast/任意路径
*.后缀名 http://localhsot:8080/day20/任意路径.后缀名
(*.do) ( http://localhsot:8080/day20/任意路径.do)
和servletconfig对象类似,这个FilterConfig对象加载初始化参数内容
FilterChain对象叫做过滤器链对象。
什么是过滤器链? 当一个资源被多个过滤器所过滤,那么就形成了一个过滤器链。
1)编写一个BufferedReader装饰者类,继承被装饰者类。(不能是final的) * 2)在装饰类中定义一个成员变量,用于接收被装饰者类的对象。 * 3)在装饰者类的构造方法中传入被装饰者类,使用第二步定义的变量接收被转入的 被装饰者类。 * 4)在装饰类类中重写被装饰者类方法,对其方法进行增强。 |
用户浏览一个网页:
服务器-> 发送一个网页的内容给用户(1k)
一个用户一天访问10页: 服务器输出10kb内容
网站1天10万用户:
100000 * 10kb = 1 000 000kb = 1GB
消耗网络带宽。
服务器(PC机):
存放网站(网页)
服务器收费的:
按流量收费的。如果尽量减少服务器向用户输出数据量,从而减少消耗带宽。
要求:在不影响用户浏览效果前提下,减少服务器输出的数据???
这时就要用到网页内容的压缩技术!!!!
压缩网页的技术: gzip压缩技术
GZIPOutputStream类进行网页内容压缩
总结:
过滤器: 在请求资源和响应资源时,执行过滤任务。
什么是过滤器链?
装饰者模式
GUI编程模式:
AWT:重量级GUI。和操作系统耦合。
SWING:轻量级GUI。和操作系统耦合低。
GUI事件编程的三要素:
事件源:JButton / Button 、 JFrame/Frame 组件
事件:KeyEvent 、 WindowEvent 、 MouseEvent
监听器:KeyListener 、WindowListener、MouseListener
web开发事件编程三要素:
事件源: ServletContext 、ServletRequest、HttpSession
事件:
对象创建或销毁:ServletContextEvent、ServletRequestEvent、 HttpSessionEvent
操作属性: ServletContextAttributeEvent、ServletRequestAttributeEvent、 HttpSessionBindingEvent
事件监听器:
ServletContextListener 、 ServletRequestListener......
web开发监听器: 开发者在web开发的过程中,会频繁使用到三个对象。ServletContext、ServletRequest、HttpSession。在创建或销毁这些对象时,或者对这些对象进行属性操作时,触发一些事件,就可以使用web监听器去监听这些事件。
web事件源 |
web事件对象 |
web事件监听器 |
ServletContext对象 |
ServletContextEvent 创建或销毁对象时触发 |
ServletContextListener |
ServletContext属性 |
ServletContextAttributeEvent 属性操作(增加属性,修改属性,删除属性) |
ServletContextAttributeListener |
ServletRequest对象 |
ServletRequestEvent 创建或销毁对象时触发 |
ServletRequestListener |
ServletRequest属性 |
ServletRequestAttributeEvent 属性操作(增加属性,修改属性,删除属性) |
ServletRequestAttributeListener |
HttpSession对象 |
HttpSessionEvent 创建或销毁对象时触发 |
HttpSessionListener |
HttpSession属性 |
HttpSessionBindingEvent 属性操作(增加属性,修改属性,删除属性) |
HttpSessionAttributeLisener |
ServletContextListener主要用于监听ServetContext对象的创建和销毁。
ServletContext对象:代表整个web应用。
创建:web应用加载
销毁:web应用重新加载或web服务停止。
步骤:
1)创建java类,实现ServletContextListener接口,实现其中的方法
2)监听器要交给tomcat服务器运行。
需要在web.xml文件中进行配置
<listener>
<listener-class>gz.itcast.a_context.MyContextListenerlistener-class> listener> |
ServletContextAttributeListener用于监听ServletContext对象的属性操作(增加属性,修改属性,删除属性)
增加属性: setAttribute(name,Object); 第一次就是增加属性
修改属性: setAttribute(name,Object); 如果前面有增加了同名的属性,则修改。
删除属性: removeAttribute(name);
/** * ServletContext对象 * @author APPle * 应用: 项目需要初始化工作和清理工作 * 需求: 在项目启动时,创建学生表;项目停止时,把学生表删除。 * */ public class MyContextListener implements ServletContextListener,ServletContextAttributeListener{
StudentDao stuDao = new StudentDao(); /** * 用于监听ServletContext对象的创建。ServletContext对象创建时调用次方法 */ public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContext对象创建了");
//创建学生表 stuDao.initTable();
System.out.println("表创建成功!"); }
/** * 用于监听ServletContext对象的销毁。ServletContext对象销毁时调用次方法 */ public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContext对象销毁了");
//删除学生表 stuDao.dropTable();
System.out.println("表删除成功!"); }
/******************属性相关的**********************/
/** * 属性添加 */ public void attributeAdded(ServletContextAttributeEvent scab) { //1.得到属性名称 String name = scab.getName(); //2.得到属性值 Object value = scab.getValue(); System.out.println("属性添加:"+name+"="+value); }
/** * 属性修改 */ public void attributeReplaced(ServletContextAttributeEvent scab) { //1.得到属性名称 String name = scab.getName(); //2.得到修改前的属性值 //Object value = scab.getValue(); //2.得到修改后的属性值 ServletContext context = scab.getServletContext(); Object value = context.getAttribute(name); System.out.println("属性修改:"+name+"="+value); }
/** * 属性删除 */ public void attributeRemoved(ServletContextAttributeEvent scab) { //1.得到属性名称 String name = scab.getName(); //2.得到属性值 Object value = scab.getValue(); System.out.println("属性删除:"+name+"="+value); }
} |
ServletRequestListener用于监听ServletRequest对象的创建和销毁。
ServletRequest对象:封装请求信息
创建:每次发出请求时
销毁:请求完毕后
ServletRequestAttributeListener用于监听ServletRequest属性操作
增加属性: setAttribute(name,Object); 第一次就是增加属性
修改属性: setAttribute(name,Object); 如果前面有增加了同名的属性,则修改。
删除属性: removeAttribute(name);
/** * ServletRequest对象 * @author APPle * 应用: 得到请求相关信息 */ public class MyRequestListener implements ServletRequestListener,ServletRequestAttributeListener{
/** * 用于监听ServletRequest对象的创建。ServletRequest对象创建时调用次方法 */ public void requestInitialized(ServletRequestEvent sre) { System.out.println("request对象创建了"+sre.getServletRequest());
//得到客户的IP地址 HttpServletRequest request = (HttpServletRequest)sre.getServletRequest(); String ip = request.getRemoteHost();
//把ip地址放入到session域中 HttpSession session = request.getSession(); session.setAttribute("ip", ip); }
/** * /** * 用于监听ServletRequest对象的销毁。ServletRequest对象销毁时调用次方法 */ public void requestDestroyed(ServletRequestEvent sre) { System.out.println("request对象销毁了"+sre.getServletRequest()); }
/************属性相关的*****************/
/** * 属性添加 */ public void attributeAdded(ServletRequestAttributeEvent srae) { String name = srae.getName(); Object value = srae.getValue(); System.out.println("属性增加: "+name+"="+value); }
/** * 属性修改 */ public void attributeReplaced(ServletRequestAttributeEvent srae) { String name = srae.getName(); //修改后的值 ServletRequest request = srae.getServletRequest(); Object value = request.getAttribute(name); System.out.println("属性修改: "+name+"="+value); }
/** * 属性删除 */ public void attributeRemoved(ServletRequestAttributeEvent srae) { String name = srae.getName(); Object value = srae.getValue(); System.out.println("属性删除: "+name+"="+value); } } |
HttpSessionListener用于监听HttpSession对象的创建和销毁
HttpSession对象:
创建:调用request.getSession()方法
销毁:
1)默认情况下,30分钟服务器自动回收
2)设置有效时长: setMaxActiveInterval(秒);
3)web.xml配置全局的过期时间
4)手动销毁: invalidate()方法
案例: 粗略统计在线访客人数
/** * HttpSession对象 * @author APPle * */ public class MySessionListener implements HttpSessionListener,HttpSessionAttributeListener{
int count = 0; /** * 监听HttpSession对象的创建。 */ public void sessionCreated(HttpSessionEvent se) { System.out.println("session对象创建了"+se.getSession());
/** * 使用代码锁避免线程并发问题 */ synchronized (MySessionListener.class) { /** * 访客人数+1 */ count++; System.out.println("当前访客人数:"+count); //保存到context域中 ServletContext context = se.getSession().getServletContext(); context.setAttribute("visit", count); }
}
/** * 监听HttpSession对象的销毁 */ public void sessionDestroyed(HttpSessionEvent se) { System.out.println("session对象销毁了"+se.getSession());
synchronized (MySessionListener.class) { /** * 访客人数-1 */ count--; //保存到context域中 ServletContext context = se.getSession().getServletContext(); context.setAttribute("visit", count); } }
/**************属性相关的******************/
/** * 属性添加 */ public void attributeAdded(HttpSessionBindingEvent se) { String name = se.getName(); Object value = se.getValue(); System.out.println("属性增加: "+name+"="+value); }
/** * 属性修改 */ public void attributeReplaced(HttpSessionBindingEvent se) { String name = se.getName(); //得到修改后的值 HttpSession session = se.getSession(); Object value = session.getAttribute(name); System.out.println("属性修改: "+name+"="+value); }
/** * 属性删除 */ public void attributeRemoved(HttpSessionBindingEvent se) { String name = se.getName(); Object value = se.getValue(); System.out.println("属性删除: "+name+"="+value); } } |
HttpSessionAttributeLisener用于监听HttpSession的属性操作
增加属性: setAttribute(name,Object); 第一次就是增加属性
修改属性: setAttribute(name,Object); 如果前面有增加了同名的属性,则修改。
删除属性: removeAttribute(name);
案例:显示当前网站的登录用户信息
需求:
1)提供用户登录的功能,提供注销功能。
2)显示当前网站的所有登录用户信息
3)管理员可以踢除指定的登录用户。
软件国际化,合适不同的国家或地区的人使用。
软件的文字:中文 、英文 、 韩文、 日文
软件的日期:
中国: 2015年1月16日 xx时xx分xx秒
美国: Jan 16 2015
英国: 16 Jan 2015
ResourceBundle类:
/** * 文字国际化 * @author APPle * */ public class Demo1 {
public static void main(String[] args) { //创建ResourceBundle /** * 参数一: 资源包的路径: 资源包名称即可! * 参数二: 国家或地区的语言环境(代码) * 查询顺序: 指定国家 -> 系统默认国家 -> 默认文件 */ ResourceBundle rb = ResourceBundle.getBundle("gz/itcast/util/message", Locale.US);
//读取资源包信息 String username = rb.getString("username"); String password = rb.getString("password"); String login = rb.getString("login"); System.out.println(username+"\t"+password+"\t"+login); }
} |
DateFormat类:
/** * 日期国际化 * @author APPle * */ public class Demo2 {
/** * @param args */ public static void main(String[] args) { /** * 参数一: 日期样式(short->medium->long->full) * 参数二: 时间样式(short->medium->long->full) * 参数三: 国家或地区语言环境 */ DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.UK);
String curDate = df.format(new Date()); System.out.println(curDate); }
} |
回顾重点内容
监听器: Listener
ServletContext对象:
ServletContextListener: 创建和销毁
ServletContextAttributeListener:属性增加,修改,删除
ServletRequest对象:
ServletRequestListener:创建和销毁
ServletRequestAttributeListener: 属性增加,修改,删除
HttpSession对象:
HttpSessionListener:创建和销毁
HttpSessionAttributeListener: 属性增加,修改,删除
注意:
1)实现特定的接口
2)监听必须交给tomcat服务器运行。在web.xml注册监听器
国际化:
文字的国际化:
javase: ResourceBundle类,通过加载不同国家的资源包实现国际化
javaee:
日期时间国际化:
javase: DateFormat类,通过设置DateForamt的日期和时间类型实现国际化
javaee:
html / css/ js
servlet / jsp
数据库相关 / jdbc
web加强: filter,listener,文件上传下载,javamail组件,在线支付
1)必须提交表单,而且这个表单必须使用post提交方式提交。
2)表单里面必须有一个,,使用file组件来上传文件
3)表单的enctype必须设置为multipart/form-data内容
<form action="${pageContext.request.contextPath }/UploadDemo1" method="post" enctype="multipart/form-data"> 请选择上传的文件1:<input type="file" name="attachment1"/> 请选择上传的文件2:<input type="file" name="attachment2"/> <input type="submit" value="上传"/> form> |
/** * 模拟服务器处理上传过来的文件(手动解析文件内容) * @author APPle * */ public class UploadDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1)获取实体内容 InputStream in = request.getInputStream(); //2)转为字符输入流 BufferedReader br = new BufferedReader(new InputStreamReader(in)); //读取一行
//文件开始分割符 String fileTag = br.readLine();
String str = br.readLine(); //获取文件名称 String fileName = str.substring(str.indexOf("filename=\"")+10,str.length()-1);
//跳过两行 br.readLine(); br.readLine();
//读取文件内容 String content = null;
BufferedWriter bw = new BufferedWriter(new FileWriter("e:/"+fileName)); while( (content=br.readLine())!=null){ //遇到文件结束符号,排除文件的结束符号 if(content.equals(fileTag+"--")){ continue; }
//写出到服务器的一个文件中 bw.write(content); //写入换行符 bw.newLine(); bw.flush(); } bw.close(); br.close(); }
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
} |
2.1 使用步骤
1)导入jar包
commons-fileupload-1.2.2.jar 核心包
commons-io-2.1.jar 辅助包
2)核心的API
DiskFileItemFactory类: 用于设置上传文件的缓存大小和缓存目录。
ServletFileUpluad类: 用于解析上传的所有文件
List
FileItem对象: 代表一个上传的文件
文件名称
大小
类型
内容
....
//处理文件 for (FileItem item : list) { String name = item.getName(); long size = item.getSize(); //b String contentType = item.getContentType();
//封装文件信息 FileBean bean = new FileBean(); /** * e.g. 1024b=1KB 1024*1024b=1MB */ String sizeStr = ""; if(size>=1024 && size<1024*1024){ sizeStr = (size/1024.0)+"KB"; }else if(size>1024*1024 && size<=1024*1024*1024){ sizeStr = (size/(1024*1024.0))+"MB"; }else if(size >= 1024*1024*1024){ sizeStr = (size/(1024*1024.0*1024))+"GB"; }else{ sizeStr = size+"B"; } bean.setName(name); bean.setSize(sizeStr); bean.setAddTime(sdf.format(new Date())); bean.setType(contentType); fileList.add(bean);
//把文件保存到硬盘 FileUtils.copyInputStreamToFile(item.getInputStream(), new File("e:/files/"+name)); //删除临时文件 item.delete(); }
request.setAttribute("fileList", fileList); //跳转到成功页面,显示成功的所有文件列表 request.getRequestDispatcher("/success.jsp").forward(request, response); |
ServletFileUpload.setHeaderEncoding("utf-8");
//得到文件的类型 String contentType = item.getContentType(); if(!contentType.contains("image/")){ //非法的文件类型 throw new FileTypeErrorException("文件类型错误,请换一张!!"); } |
setFileSizeMax: 设置限制单个文件的大小
setSizeMax: 设置一个请求中所有文件的总大小
使用唯一算法生成文件名称: UUID算法
/** * 注意: 使用UUID算法生成唯一的文件名,避免因为文件名相同覆盖原有的文件 */ String uuid = UUID.randomUUID().toString(); String fileName = item.getName(); //后缀名 fileName.substring(fileName.lastIndexOf(".")); //新文件名 String newFileName = uuid+fileName; |
/** * 返回新的目录结构: e.g 11(第一级目录)/2(第二级目录) */ private String makeDirectory(String fileName){ //1.得到文件名的hashCode值 int code = fileName.hashCode();
//2.算出第一层目录的名称 int first = code & 0xF;
//3.算出第二层目录的名称 int second = code & (0xF>>1); return first+"/"+second+"/"; } |
/** * 实现ProgressListener监听文件上传进度 * */ class MyProgressListener implements ProgressListener{ /** * 1: 目前上传了多少字节 * 2: 总长度 * 3: 目前正在第几个文件 */ public void update(long pBytesRead, long pContentLength, int pItems) { System.out.println("已经上传了"+pBytesRead+"字节,总字节数为"+pContentLength+"字节,现在正在上传第"+pItems+"个文件!"); }
} |
if(item.isFormField()){ //除file组件外的其他表单组件(text/password/select/hidden。。。。。) //取出text组件内容 /** * getFieldName()获取表单组件的name属性值 */ if(item.getFieldName().equals("info1")){ String info1 = item.getString("utf-8"); System.out.println(info1); } if(item.getFieldName().equals("info2")){ String info2 = item.getString("utf-8"); System.out.println(info2); } |
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//TODO 扣除会员的积分...
//1)读取服务器的文件 //读到当前web应用的根目录下的文件,返回绝对路径 /* String path = this.getServletContext().getRealPath("/upload/1.jpg"); FileInputStream in = new FileInputStream(new File(path)); */
// String fileName = "1.jpg"; String fileName = "美女.jpg";
InputStream in = this.getServletContext().getResourceAsStream("/upload/"+fileName);
/** * 对中文的文件名加入URLEncoder加密 * 请求发送数据: request 带中文 -> 浏览器传输(URLEncoder) -》( 服务器获取(URLDecoder)) 响应发送数据: response 带中文( URLEncoder ) -> 浏览器传输 - 》 浏览器获取(URLDecoder) */ fileName = URLEncoder.encode(fileName, "utf-8"); /** * 设置一个响应头: Content-Disposition 告诉浏览器以下载的方法打开该资源 */ /** * 注意: 不同的浏览器在识别content-disposition值的内容有差异 * IE: attachment;filename=1.jpg * 非IE: attachment;filename*=1.jpg */ String userAgent = request.getHeader("user-agent"); String content = ""; if(userAgent.contains("Trident")){ //IE content = "attachment;filename="+fileName; }else{ //非IE content = "attachment;filename*="+fileName; } response.setHeader("content-disposition", content);
//2)把文件内容发送给浏览器 OutputStream out = response.getOutputStream(); byte[] buf = new byte[1024]; int len = 0; while( (len=in.read(buf))!=-1 ){ out.write(buf, 0, len); } out.close(); in.close(); |
思路:
一:上传
文件上传- > 服务器 (1、文件内容保存起来 2、文件相关信息存储到数据库)
二: 下载
1. 数据库库读取文件信息 - > 2.拿着文件路径到服务器找文件 -》 使用servlet下载
注意:文件相关信息使用FileBean对象存储
三层结构开发顺序:
1) 设计数据库
2)设计实体
3)编写dao
4)编写service
5)编写servlet+jsp
总结:
上传:
1) 必须的三个条件:
表单,提交必须是post
必须有file组件
表单的enctype改为multipar/form-data
2) 使用FileUpload组件处理文
DiskFileItemFactory
ServiletFileUpload
List
3)下载:
使用servlet进行下载
1)表单的提交方式必须是POST方式。(才有content-type属性)
2)有文件上传表单,表单中有的选择文件的标签
3)把表单设置为enctype="multipart/form-data",提交的数据不再是key-value对,而是字节数据
<form action="${pageContext.request.contextPath }/UploadDemo1" method="post" enctype="multipart/form-data"> 请选择文件: <input type="file" name="img"/><br/> <input type="submit" value="上传" /> form> |
/** * 手动处理上传文件的逻辑 * @author APPle * */ public class UploadDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //得到实体内容数据 InputStream in = request.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in));
//读取文件的开始符 String startTag = br.readLine();
//读取文件名: Content-Disposition: form-data; name="img"; filename="news.txt" String line = br.readLine(); String fileName = line.substring(line.lastIndexOf("filename=\"")+10, line.lastIndexOf("\"") ); System.out.println("文件名:"+fileName);
//跳过2行 br.readLine(); br.readLine();
//读取文件的实际内容 String str = null; BufferedWriter bw = new BufferedWriter(new FileWriter("E:/files/"+fileName)); while((str=br.readLine())!=null){ //读到文件结束符时退出循环 if((startTag+"--").equals(str)){ break; }
//把内容写出文件中 bw.write(str); bw.newLine(); bw.flush(); } //关闭 bw.close(); br.close(); }
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
} |
是Apache组织旗下的开源的文件上传的组件。使用非常简单易用。
DiskFileItemFactory类: 用于创建上传对象,设置文件缓存区大小,设置文件缓存目录。
ServletFileUpload类: 用于在Servlet程序中实现文件上传
List
FileItem类: 封装一个文件的所有相关的信息javabean。包含文件名称,文件大小,文件类型, 文件数据内容。
1)导入commoms-fileuload的jar包
commons-fileupload-1.2.2.jar 核心包
commons-io-2.1.jar 辅助包
2)编写程序
//1.创建DiskFileItemFactory类 /** * 参数一: 表示文件缓存区的大小。如果上传的文件没有超过缓存区大小,则文件不缓存;否则缓存文件,缓存到临时目录。(byte) * 参数二: 表示缓存区的临时目录。 */ DiskFileItemFactory factory = new DiskFileItemFactory(10*1024,new File("e:/temp/"));
//2.创建ServletFileUpload类 ServletFileUpload upload = new ServletFileUpload(factory);
/** * 设置文件名的编码 */ upload.setHeaderEncoding("utf-8");
//3.解析request数据(把每一个文件封装到FileItem对象中,FileItem放入List中) try { List
//取出第一个上传的文件 FileItem file = list.get(0);
//得到文件名(getName()) String fileName = file.getName(); //得到文件大小 long fileSize = file.getSize(); //得到内容类型 String contentType = file.getContentType(); //得到文件数据内容 InputStream in = file.getInputStream();
/** * 4.把文件数据内容存储到服务器端的硬盘中 */ FileUtils.copyInputStreamToFile(in, new File("e:/files/"+fileName));
/** * 5.文件上传完毕,手动清理缓存文件 */ file.delete();
System.out.println("文件名:"+fileName); System.out.println("文件大小:"+fileSize); System.out.println("文件类型:"+contentType); System.out.println("文件数据内容:"+in);
} catch (FileUploadException e) { e.printStackTrace(); } |
//1.创建DiskFileItemFactory对象 DiskFileItemFactory factory = new DiskFileItemFactory(10*1024, new File("e:/temp/")); //2.创建ServletFileUpload对象 ServletFileUpload upload = new ServletFileUpload(factory); //3.设置文件编码 upload.setHeaderEncoding("utf-8"); //4.开始解析文件 try { List if(list!=null){ List //遍历多个文件 for(FileItem file: list){ //取出文件相关信息 String fileName = file.getName(); long fileSize = file.getSize(); String contentType = file.getContentType(); //封装到javabean中 UploadFile uf = new UploadFile(); uf.setFileName(fileName); uf.setFileSize(fileSize); uf.setFileType(contentType); //放入list中 ufList.add(uf);
//把文件保存到服务器端的硬盘 FileUtils.copyInputStreamToFile(file.getInputStream(), new File("e:/files/"+fileName)); //删除缓存文件 file.delete(); } request.setAttribute("ufList", ufList); request.getRequestDispatcher("/success.jsp").forward(request, response); } } catch (FileUploadException e) { e.printStackTrace(); } |
<html> <head> <title>使用组件实现动态多文件上传title> head>
<body> <form action="${pageContext.request.contextPath }/UploadDemo3" method="post" enctype="multipart/form-data" name="uploadForm"> <table border="1" width="400px"> <tbody> <tr id="1"> <td> 请选择文件: td> <td> <input type="file" name="file"/><input type="button" value="删除" onclick="delItem(1)"/> td> tr> tbody> <tr> <td colspan="2"><input type="button" value="添加" onclick="addIten()"/>td> tr> <tr> <td colspan="2"><input type="button" value="上传" onclick="checkSunbit()"/>td> tr> table> form>
<script type="text/javascript"> var id = 2; //添加一行 function addIten(){ var trNode = document.createElement("tr"); trNode.setAttribute("id", id);
var tdNode1 = document.createElement("td"); tdNode1.innerHTML = "请选择文件:";
var tdNode2 = document.createElement("td"); var input1 = document.createElement("input"); input1.setAttribute("type", "file"); input1.setAttribute("name", "file"); var input2 = document.createElement("input"); input2.setAttribute("type", "button"); input2.setAttribute("value", "删除"); input2.setAttribute("onclick", "delItem("+id+")"); tdNode2.appendChild(input1); tdNode2.appendChild(input2);
trNode.appendChild(tdNode1); trNode.appendChild(tdNode2);
var tbodyNode = document.getElementsByTagName("tbody")[0]; tbodyNode.appendChild(trNode);
id++;
}
//删除一行(根据tr的id值删除) function delItem(id){ if(id>1){ var trNode = document.getElementById(id); var tbodyNode = document.getElementsByTagName("tbody")[0]; tbodyNode.removeChild(trNode);
id--; } }
//提交并且检查file属性 function checkSunbit(){ //检查file属性是否全部填上 var fileList = document.getElementsByName("file"); for(var i=0;i //如果为选择file,则其value值为空 if(fileList[i].value==null || fileList[i].value==""){ alert("请选择第"+(i+1)+"个文件"); return; } }
//提交表单 var form = document.forms['uploadForm']; form.submit(); } script> body> html> |
//得到文件类型 String contentType = file.getContentType(); System.out.println(contentType); //如果是图片,才可以上传(image/bmp,jepg,jpg,gif) if(!contentType.toLowerCase().matches("image/[a-z]*")){ throw new FileTypeErrorException("文件类型不符合条件!"); } |
ServletFileUpload类:
setFileSizeMax:设置单个文件的最大容量。
setSizeMax : 设置所有文件的最大容量。
//4.解析请求 try { List if(list!=null){ for(FileItem file : list){ /** * 限制文件类型 */ //得到文件类型 String contentType = file.getContentType(); System.out.println(contentType); //如果是图片,才可以上传(image/bmp,jepg,jpg,gif) if(!contentType.toLowerCase().matches("image/[a-z]*")){ throw new FileTypeErrorException("文件类型不符合条件!"); }
//保存文件 FileUtils.copyInputStreamToFile(file.getInputStream(), new File("e:/files/"+file.getName())); //删除缓存文件 file.delete(); } } } catch (FileTypeErrorException e) { //e.printStackTrace(); //处理文件类型错误的异常 request.setAttribute("message", e.getMessage()); request.getRequestDispatcher("/05.upload.jsp").forward(request, response); return; } catch (FileSizeLimitExceededException e) { //e.printStackTrace(); //处理文件超过限制的异常 request.setAttribute("message", "单个文件不能超过1M"); request.getRequestDispatcher("/05.upload.jsp").forward(request, response); return; } catch (SizeLimitExceededException e) { //e.printStackTrace(); //处理文件超过限制的异常 request.setAttribute("message", "所有文件不能超过5M"); request.getRequestDispatcher("/05.upload.jsp").forward(request, response); return; } catch (FileUploadException e) { e.printStackTrace(); } |
ServletFileUpload类:
setProgressListener(进度监听器): 设置文件上传的监听器
/** * 解决文件名重复问题: * 1)日期_时间_随机数.jpg * 2)使用UUID算法(在一台PC都是唯一的) */ String fileName = file.getName(); //得到源文件的后缀名 String supfix = fileName.substring(fileName.lastIndexOf(".")); //.jpg //使用UUID算法生成随机名称 fileName = UUID.randomUUID().toString()+supfix; |
假如所有文件都放在同一个目录下,不方便管理。
//判断该FileItem是否是文件还是普通文本空间 if(file.isFormField()){ //普通文本空间(text/password/checkbox/radio/select/texearea) //得到控件内容
//处理一个普通文本 /*String info = file.getString("utf-8"); System.out.println("描述:"+info);*/
//处理多个普通文本 String fieldName = file.getFieldName(); if("info1".equals(fieldName)){ String info1 = file.getString("utf-8"); System.out.println("描述1:"+info1); } if("info2".equals(fieldName)){ String info2 = file.getString("utf-8"); System.out.println("描述2:"+info2); } |
使用超链接。缺点:1)暴露文件的路径 2)扩展型和安全性不好
//得到需要下载的文件 String path = this.getServletContext().getRealPath("/upload/9/1/图片1.png");
File file = new File(path); //读取服务器本地的文件 FileInputStream in = new FileInputStream(file);
/** * 处理URL编码问题 */ String fileName = file.getName(); //对文件名进行URl编码 fileName = URLEncoder.encode(fileName, "utf-8");
//判断不同浏览器 String userAgent = request.getHeader("user-agent"); String filefix = null; if(userAgent.contains("Trident")){ //IE filefix = "filename="+fileName; }else if(userAgent.contains("Firefox")){ //Firefox filefix = "filename*="+fileName; }else{ filefix = "filename="+fileName; }
//告诉浏览器以下载方式打开资源 response.setHeader("Content-Disposition", "attachment;"+filefix);
//把本地文件发送给浏览器 byte[] buf = new byte[1024]; int len = 0; while( (len=in.read(buf))!=-1 ){ response.getOutputStream().write(buf, 0, len); } //关闭 in.close(); |
day23_基础加强
|
回顾重点内容:
上传和下载:
1)上传:
1.1 三个条件
a)以post方式提交的表单
b)表单内必须有
c)把表单的enctype属性设置为multipart/form-data
1.2 使用apache组织的上传工具
DiskFileItemFactory : 设置上传环境。缓存大小,缓存目录
核心类:ServletFileUpload: 用于解析上传的文件
List
FileItem对象:代表一个上传后的文件。
名称,大小,类型,内容.....
FileItem.isFormFiled(): 判断该文件是否是普通表单组件。
FileItem.getString(): 获取普通表单组件的内容
细节:
1)设置限制文件大小:
ServletFileUpload.setFileSizeMax() : 设置单个文件
ServletFileUpload.setSizeMax() 设置总文件
2)设置文件名编码
ServletFileUpload.setHeaderEncoding("utf-8);
3)设置上传文件的进度监听器
ServletFileUpload.setProgressListener(监听器的实现类);
2)下载文件:
使用servlet进行文件下载:
设置响应头,通知浏览器以下载方式打开文件
response.setHeader("content-disposition,"attachment;filename=xxx.jpg");
1)读取到服务器文件
InputStream in = xxxxxxxx
2)把文件流输出到浏览器作为响应的实体内容
OutputStream out = response.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while( (len=in.read(buf))!=-1 ){
out.wirte(buf,0,len);
}
大纲:
1)Junit单元测试的使用
2)枚举
3)复习反射
4)复习泛型
5)反射泛型(新)
6)注解
7)反射注解(新)
8)类加载器
Junit单元测试,主要用于对程序进行专业化测试
/** * 写一个测试MathUtil的add方法的测试方法 * 注意: 给一个方法加上@Test注解,那么该方法就是一个测试方法(类似于一个main方法) * * 1)Junit测试方法的规则: * 1)测试方法一定在方法顶部要加上@Test注解 * 2)方法必须是public修饰,不能有返回值,不能有参数,可以抛出异常 * 2)Junit方法如何运行? * 1)运行一个方法: 双击对应的方法,右键Run As-》Junit Test (或者Outline视图,选择方法Run As-》Junit Test) * 2)运行类的所有方法:双击类,右键Run As-》Junit Test * 3)运行一个项目的所有方法:双击项目,右键Run As-》Junit Test * 3)查看Junit方法的结果 * 查看Junit的导航条: * 绿色: 代表测试通过了! * 红色: 代表测试不通过! * 4)对结果进行判断 * Assert断言类: * Assert.assertEquals 判断是否相等,使用equals方法比较 * assertSame 判断是否相等,使用==比较 */ @Test public void testAdd()throws Exception{ MathUtil mu = new MathUtil(); int result = mu.add(10,5); /** * 手动判断结果(不专业) */ /*if(result!=15){ throw new RuntimeException("错误结果"); }*/
Person p1 = new Person("eric",20); Person p2 = new Person("eric",20);
/** * 使用Junit专业的判定结果的方法: * Assert类: 断言类(判定结果是什么??) */ //Assert.assertEquals(15, result);// 判断两个值是否相等。相等时,测试通过,否则,测试不通过. //Assert.assertNotSame(15, result);// 判断两个值是否不相等。不相等时,测试通过,否则,测试不通过. //Assert.assertSame(15, result);// 判断两个值是否相等。相等时,测试通过,否则,测试不通过.
//Assert.assertEquals(p1, p2); // 可以比较的是对象内容.用equals方法进行比较 Assert.assertSame(p1, p2); // 比较的是对象内存的地址. 用==比较比较 } |
/** * 枚举类型:(jdk1.5以后出现的类型) */ enum Score{ A,B,C,D,E; } |
/** * 枚举类型:(jdk1.5以后出现的类型) * A (100-90) B(89-80) C(79-70) D(69-60) E(59-0) */ enum Score{ A("100-90"),B("89-80"),C("79-70"),D("69-60"),E("59-0");
private String result;//分数段 //private Score(){}; /** * 有参数的构造方法 */ private Score(String result){ this.result = result; }
public String getResult(){ return result; }
} |
/** * 得到枚举中所有值:每个枚举类型都有一个values()方法 */ Score[] arr =Score.values(); for (Score score : arr) { System.out.println(score); } |
Class类:代表类。
作用: 到类名称,类的实现的接口,继承的类..
Constructor类:代表类的构造方法
作用: 构造对象
Method类:代表类的普通方法
作用:调用方法
Field类: 代表类的属性。
作用: 得到属性值,设置属性值
@Test public void test()throws Exception{ //1)得到Class类的对象 //方式一: //Class clazz = Student.class; //方式二: (依赖性比较低,只依赖字符串) Class clazz = Class.forName("gz.itcast.c_reflect.Student"); //方式三: //Class clazz = new Student().getClass();
//得到类名 System.out.println(clazz.getName());//全名(包含包结构) System.out.println(clazz.getSimpleName());//简单名
//得到类的继承结构 //得到Student类的父类 Class parent = clazz.getSuperclass(); System.out.println(parent.getSimpleName());
//得到类的接口 Class[] interArray = clazz.getInterfaces(); for (Class cla : interArray) { System.out.println(cla.getSimpleName()); } } |
@Test public void test()throws Exception{ //1)通过Class类得到Constructor类 Class clazz = Class.forName("gz.itcast.c_reflect.Student"); /** * 1)调用无参数的构造方法 */ //根据不同的参数列表获取不同的构造方法(Constructor)对象 Constructor cons = clazz.getConstructor(null);
//2)通过Constructor类的方法构造对象 Object obj = cons.newInstance(null);
System.out.println(obj.getClass().getSimpleName());
/** * 调用有参数的构造方法 */ Constructor cons2 = clazz.getConstructor(int.class,String.class); Object obj2 = cons2.newInstance(10,"eric"); System.out.println(obj2.getClass().getSimpleName()); System.out.println(obj2); } |
@Test public void test() throws Exception{ //1)通过Class类对象得到Method类对象 Class clazz = Class.forName("gz.itcast.c_reflect.Student"); Object obj = clazz.getConstructor(null).newInstance(null); /** * getMethod() :获取类上的公共的方法(public) * getDeclaredMethod(): 获取类上的所有方法(公共和私有的方法) */ /** * 参数一: 方法名 * 参数二: 形式参数列表 */ Method setName = clazz.getMethod("setName", String.class);
//2)通过Method对象调用方法 /** * 参数一: 调用方法所需的对象 * 参数二:实际参数值 */ setName.invoke(obj, "jacky"); // obj.setName("jacky")
System.out.println(obj);
Method getName = clazz.getMethod("getName", null); //调用方法,接收方法返回值 Object result = getName.invoke(obj, null); // obj.getName();
System.out.println(result); } |
@Test public void test() throws Exception{ //1)通过Class得到Field类对象 Class clazz = Class.forName("gz.itcast.c_reflect.Student"); //Object obj = clazz.getConstructor(null).newInstance(null); Object obj = clazz.newInstance();//这是简化版本,但只能使用无参的构造方法构造 /** * getDeclaredField:得到任意修饰的属性(public/private) * getField(): 得到公开的属性(public) */ Field name = clazz.getDeclaredField("name");
//2)得到属性名 System.out.println(name.getName());//name //得到属性的类型 System.out.println(name.getType()); //String
/** * 给属性赋值: * 以前: s.name="eric" * 现在:使用反射技术直接类似地直接给属性赋值,而不是调用setName方法赋值 */ /** * 参数一: 赋值给哪个对象 * 参数二: 赋值 */ //打破私有修饰符的限制 name.setAccessible(true);//忽略private修饰符
name.set(obj, "rose");
/** * 获取属性值 */ Object result = name.get(obj); System.out.println(result); } |
泛型的作用, 泛型语法(泛型方法,泛型类,泛型接口),泛型关键字(extends/super),反射泛型
泛型
1)可以减少手动类型转换的工作
2)可以把程序运行时错误提前到编译时报错!!
好处: 使用泛型可以让开发者写出更加通用的代码!!!!!!!
@Test public void test(){ /** * jdk1.4或以前的做法 */ List list = new ArrayList(); //存储 list.add(new Cat()); list.add(new Dog());
//取出 Cat cat = (Cat)list.get(0); //运行的时候报错,类型转换错误 //Cat cat2 = (Cat)list.get(1);
/** * jdk1.5 之后 * 泛型的作用1: 把运行的可能经常导致的类型转换错误,提前到编译时检测类型。 * 泛型的作用2: 减少手动类型转换的工作 */ List list2.add(new Cat()); //编译的时候报错,类型无法存入List集合 //list2.add(new Dog());
List list3.add("eric"); list3.add("jacky"); list3.add("rose");
/** * jdk1.4或以前的做法 */ //遍历 for (Object obj : list3) { //如果这时需要调用String特有的方法 String str = (String)obj;
System.out.println(obj); }
/** * jdk1.5之后的做法 */ for (String obj : list3) {
System.out.println(obj); } }
|
/** * 设计一个通用的方法,可以接收任何类型 * 泛型方法的作用是可以让开发者设计出更加通过的方法 * @param dept */ public return t; }
注意: 泛型方法在调用方法时确定类型 |
/** * 泛型方法和泛型类
* @author APPle * */ public class Demo2
/** * 设计一个通用的方法,可以接收任何类型 * 泛型方法的作用是可以让开发者设计出更加通过的方法 * @param dept */ public T save(T t,K k){ return t; }
public void update(T t,K k){
} |
注意:泛型类的定义了泛型,那么方法上如果使用的同一个类型,那么方法就不需要定义泛型
|
泛型类的类型是在创建类的对象时确定!! |
/** * 泛型接口 * 泛型接口的类型确定: * 1)直接实现泛型接口的时候可以确定类型 * 2)继承泛型接口的实现类的时候可以确定类型 * @author APPle */ public class Demo3 {
}
class Employee{} /*通用的dao接口*/ interface IBaseDao public void save(T t); public void update(T t); } /** * 具体的业务dao * @author APPle * */ /*class EmpDao implements IBaseDao
@Override public void save(Employee t) {
}
@Override public void update(Employee t) {
} }*/
/** * 通用的dao实现类 */ class BaseDao
@Override public void save(T t) { //写通用的增加方法 }
@Override public void update(T t) { //通用的修改方法 } }
/** * 具体的业务dao实现类 * @author APPle * */ class EmpDao extends BaseDao } |
用于限定泛型的使用范围!!!!
/** * 没有加上泛型,则会报警告,希望保持泛型特征 * 注意: * 如果一个泛型类加上?号泛型,则该类不能再进行编译,只能用于接收数据 */ List> list = getList(); |
/** * 该方法只能接收存放者Number类型的子类对象的List集合 * extends : 只能传入指定类对象或者指定类的子类 * @param list */ public void add(List extends Number> list){
}
|
/** * 该方法只能接收存放者Number类型的父类对象的List集合 * super: 只能传入指定类的对象或者指定类的父类 * @param list */ public void add2(List super Number> list){
} |
/** * 需要解决的问题: * 约定: 具体泛型类型的类名 和 表名 保持一致!!!! * 1) 得到具体的业务dao运行过程中的泛型具体类型(Student/Teacher),可以封装ResultSet * 2) 得到泛型具有类型名称 ,就是表名 */ //1)this : 代表当前运行的dao对象 //System.out.println(this.getClass()); //2)this.getClass(): 代表当前运行dao对象的Class对象 Class clazz = this.getClass(); //public class TeacherDao extends BaseDao //3)clazz.getGenericSuperclass(): 得到当前dao对象的父类(参数化类型) Type type = clazz.getGenericSuperclass(); // BaseDao //4)把父类的类型强转成子类(参数化类型: ParameterizedType) ParameterizedType param = ( ParameterizedType)type; // BaseDao //5)param.getActualTypeArguments():得到参数化类型 上面的泛型类型列表 Type[] types = param.getActualTypeArguments(); // //6)取出泛型类型列表中的第一个泛型类型 Type target = types[0]; // Teacher //7)强制成Class类型 targetClass = (Class)target; |
Annotation
注解:给程序带上一些标记,从而影响编译器运行程序的结果!!!
注释:提高程序的可读性,对程序运行没有影响!!!
注解的作用:
1)可以在程序上(类,方法,属性)携带信息
2)注解简化(取代)配置文件(xml或properties)
每个框架都有两种方案:
1)把参数放在xml文件中
2)把参数放在注解中
/** * 1)告诉编译器强制对方法进行覆盖 */ @Override public String toString() { return super.toString(); }
/** * 2)告诉编译器压制代码中出现的警告 * @return */ @SuppressWarnings(value = { "unchecked" }) public List save(){ List list = new ArrayList(); return list; }
/** * 3)在运行时让方法提示过期 */ @Deprecated public void
} |
5.2 定义注解语法
public @interface Author { } |
public @interface Author {
//声明属性 String name(); String modifyTime(); } |
1)属性的类型可以基本数据类型。也可以是数组类型
2)使用default关键子给注解一个默认值
3)当注解中使用value名称的属性,则可以省略“value=”不写
public @interface Author {
//声明属性 String name(); String modifyTime() default "2015-06-25";//给属性带上默认值 String[] address();//带有数组类型的属性 //如果注解的属性名称为value,那么在使用注解的时候可以不写value= String[] value(); //String[] names(); //String value(); } |
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
声明注解的使用范围.
TYPE: 注解可以用在类上
FIELD:注解可以用在属性上
METHOD:用在方法上
PARAMETER:用在参数声明上面
CONSTRUCTOR:用在构造方法上面
LOCAL_VARIABLE:用在本地变量上面
@Retention(RetentionPolicy.SOURCE)
声明注解的有效范围
RetentionPolicy.SOURCE: 该注解只在源码中有效!
RetentionPolicy.CLASS: 该注解在源码中,和字节码中有效!(默认)
RetentionPolicy.RUNTIME: 该注解在源码中,和字节码中有效,运行字节码的时候有效!
使用反射技术获取(类,方法,属性)注解的信息
//1)得到save方法对象 Method m = this.getClass().getMethod("save", null);
//2)得到方法上面的注解 Author author = m.getAnnotation(Author.class); System.out.println(author);
//3)获取注解里面的属性(数据) String name = author.name(); String time = author.modifyTime(); System.out.println(name); System.out.println(time); |
改造泛型DAO的案例:
问题: 当具体的实体类对象的属性和表的字段不一致时,BaseDao就无法使用了!!这时需要使用注解绑定类和表名,属性和字段的关系。改造后的实体对象如下
/** * 表的注解 * @author APPle * */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String name();//数据的表名 } |
/** * 字段的注解 * @author APPle * */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String name();//字段名称 } |
@Table(name = "teacher_list") public class Teacher {
@Column(name = "tid") private int id;
@Column(name = "tname") private String name;
@Column(name = "tage") private int age;
public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Teacher [age=" + age + ", id=" + id + ", name=" + name + "]"; } } |
总计:
1)junit使用
2)复习反射
3)反射泛型(重点)
4)注解
5) 反射注解(重点)
6)类加载器
回顾重点内容
基础加强:
1)复习反射内容: 好处:依赖性更低!!!!
Class类:代表类
作用:得到类名,继承什么类,实现了什么接口,得到方法,得到属性,得到构造 方法
Class.forName("");
this.getClasss()
类.class;
Constructor类:代表类的构造函数
作用:构造对象(无参或有参)
Class.forName("类名").getConstructor(参数列表).newInstance(实际参数);
Method类: 代表类的普通方法
作用: 调用普通的方法
Class.forName("类名").getMethod("方法名",参数列表).invoke(对象,实际参数);
Field类: 代表类的属性
作用: 得到属性名,属性类型,获取属性值,设置属性值
Field.set(对象,值); 设置
Field.get(对象); 获取
2)复习泛型
2.1 作用: 把运行时的异常提前到编译时,减少手动类型转换工作
2.2 好处 : 写出更加通用的方法或类
2.3 泛型语法: 泛型方法,泛型类,泛型接口
2.4
extends: 是指定类型 或 指定类型的子类
super: 是指定类型 或 指定类型的父类
3)反射泛型: 用反射技术得到泛型的类型
Type type = this.getClass().getGenericSuperClasss(); //得到父类的类型(包含“参数化类型”)
BaseDao
ParameterizedType param = (ParameterizedType)type; //强制成参数化子类
Type[] types = param.getActualTypeArguments(); //得到参数化类型的泛型参数列表
types[0]
4)注解:
作用: 在程序中携带信息,甚至可以简化或取代xml配置文件
public @interfaec Author{
String name();
}
5) 反射注解: 使用反射技术得到程序上的注解信息
Class.getAnnotaion()
Method.getAnnotation()
Constuctor.getAnnotaion()
Field.getAnnotation();
在互联网的兴起后,才逐步发展的。
B2B
B2C
C2C
O2O online to offline
smtp: simple mail transfer protocol
1) Session类:该类用于创建一个和邮箱服务器的连接和验证登录。
2)MimeMessage类: 该类代表一封邮箱(收件人,发件人,主题,正文。。。)
3) Traspoart类: 发送邮件
send(MimeMessage): 发送邮件的方法。
//1)创建一个Session对象,连接和登录服务器 /** * 参数一: 本次连接的配置。 * 参数二: 返回对用户名和密码base64加密的对象 */ Properties props = new Properties(); //1.1连接的发邮件的服务器地址 props.setProperty("mail.host", "smtp.126.com"); //1.2 指定进行验证登录 props.setProperty("mail.smtp.auth", "true");
Session session = Session.getDefaultInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("[email protected]","eric12345"); } }); //打开调用 session.setDebug(true);
//2)在本次连接上, 创建一封邮件 MimeMessage mail = new MimeMessage(session);
//3)设置邮件内容 ///3.1 设置发件人 mail.setFrom(new InternetAddress("[email protected]"));
//3.2 设置收件人 /** * 参数一: 发送方法 * 发送: TO A->B * 抄送: CC A->B C * 密送: BCC A->B C * 参数二: 发送的地址 */ mail.setRecipient(RecipientType.TO, new InternetAddress("[email protected]"));
//3.3 设置主题 mail.setSubject("这是一封javamail的测试邮件");
//3.4 设置内容 /** * 参数二: 邮件的内容格式。如 普通文本,html方式 */ mail.setContent("这是邮件的正文内容", "text/plain;charset=utf-8");
//4)发送邮件 Transport.send(mail); |