前面讲到PrepareStatement可以用来解决SQL注入的问题,除了这个好处之外,PrepareStatement还有另外两个好处:一是Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出,而PrepareStatement不会出现这种情况。二是数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。既然PrepareStatement好处这么多,是不是不需要用到Statement了呢?当SQL语句中带有传入的参数时,特别是字符串类型的参数,就最好是使用PrepareStatement,如果是将查询、删除、更新、插入条件写在SQL语句里面,就可以使用Statement,其它情况最好使用PrepareStatement,这样可以避免SQL注入的问题,也有上面提到的两个好处。
接下来讲到的是日期内型的转换。因为web应用程序大多是多层架构,数据库属于持久层,使用的日期类型是java.sql包里的Date类型,该类继承自java.util包中的Date类,主要区别是sql包里的只有日期,而util包中的Date对象既有日期又有时间。而JDBC是用来访问数据库的,属于数据访问层,它是为上一层业务逻辑层服务的。业务逻辑层是不需要与数据库打交道的,所以业务逻辑层里用到的Date类型都是util包中的。所以在JDBC这一层就需要将这两种类型进行转换。下面代码模拟一下这个问题,create方法中的参数由业务逻辑层传递过来:
static void create(String name, Date birthday, float money)
throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "insert into user(name,birthday, money) values (?, ?, ?) ";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setDate(2, new java.sql.Date(birthday.getTime()));
ps.setFloat(3, money);
int i = ps.executeUpdate();
System.out.println("i=" + i);
} finally {
JdbcUtils.free(rs, ps, conn);
}
}
上面的代码,除了ps的设置语句,其它全是模板代码,都是从前面的例子中截取的,这就是使用模板工具类的好处。和前面讲解的一样,?是占位符,以便在后面的代码中设置问号处的参数,每个问号都一次对应一个索引值,从序号1开始。在设置语句中,如果直接用ps.setDate(2, birthday)将会出错,因为ps中setDate方法接受的参数的sql包中的类型,而birthday是util包中的类型。所以要经过上面的类型转换才能正确插入数据库中。这是写入数据库时的转换,从数据库读出时,可以不转换,因为两个日期类型是父子关系,所以父类的引用的可以指向子类的对象。下面两句用在读出操作里面都是可以的:
birthday = new Date(rs.getDate("birthday").getTime());
birthday = rs.getDate("birthday");
但是上面两句虽然都能通过编译,输出的结果还是有差别的。第二句输出的是只有日期的Date,第一句带有标准的时间。除了日期类型,其它基础类型都比较简单,所以接下来要讲解的是两个感觉上复杂一些的类型。在mysql中,默认情况下varchar类型是45个字符的长度,但是最多也只能容纳255个字符,所以不适合存储大量的文本信息。解决方式是用CLOB 类型来表示。但是不同的数据库对这种类型的描述名称有差异,而且可能不只一种。在mysql中,其中的一种表示CLOB类型的名称是TEXT,视频中演示了该类型的使用,同时创建了一个辅助的表clob_test(id,big_text),其中id是整数,big_text是TEXT类型的。
static void create() throws SQLException, IOException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "insert into clob_test(big_text) values (?) ";
ps = conn.prepareStatement(sql);
File file = new File("src/JdbcUtils.java");
Reader reader = new BufferedReader(new FileReader(file));
ps.setCharacterStream(1, reader, (int) file.length());
int i = ps.executeUpdate();
reader.close();
System.out.println("i=" + i);
} finally {
JdbcUtils.free(rs, ps, conn);
}
}
}
上面的代码显示,与基本类型的主要差别就是文件的输入操作。通过JAVA IO操作,将文件包装成字符流封装在一个Reader对象中,然后调用ps的setCharacterStream方法输入字符流到数据库。setCharacterStream有三个参数,第一个是占位符的索引号,第二个就是字符流对象,最后一个就是文件的长度。如果能确定字符流中全是ASCII字符,则可以调用setAsciiStream方法来写入ASCII字符流到数据库中。了解了写入数据库中,另外一半就是从数据库中读出来。除了模板代码,关键的代码如下:
rs = st.executeQuery("select big_text from clob_test");
while (rs.next()) {
Clob clob = rs.getClob(1);
Reader reader = clob.getCharacterStream();
File file = new File("JdbUtils_bak.java");
Writer writer = new BufferedWriter(new FileWriter(file));
char[] buff = new char[1024];
for (int i = 0; (i = reader.read(buff)) > 0;) {
writer.write(buff, 0, i);
}
writer.close();
reader.close();
}
一个是执行语句是最简单的查询语句,然后就是结果集的处理。先根据查询的列从结果集中得到Clob对象,然后通过IO操作将数据写入到硬盘上。当然实际中也可以写入到其他地方,上例只是演示了如何获取大的文本对象。
接下来讲到的是日期内型的转换。因为web应用程序大多是多层架构,数据库属于持久层,使用的日期类型是java.sql包里的Date类型,该类继承自java.util包中的Date类,主要区别是sql包里的只有日期,而util包中的Date对象既有日期又有时间。而JDBC是用来访问数据库的,属于数据访问层,它是为上一层业务逻辑层服务的。业务逻辑层是不需要与数据库打交道的,所以业务逻辑层里用到的Date类型都是util包中的。所以在JDBC这一层就需要将这两种类型进行转换。下面代码模拟一下这个问题,create方法中的参数由业务逻辑层传递过来:
static void create(String name, Date birthday, float money)
throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "insert into user(name,birthday, money) values (?, ?, ?) ";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setDate(2, new java.sql.Date(birthday.getTime()));
ps.setFloat(3, money);
int i = ps.executeUpdate();
System.out.println("i=" + i);
} finally {
JdbcUtils.free(rs, ps, conn);
}
}
上面的代码,除了ps的设置语句,其它全是模板代码,都是从前面的例子中截取的,这就是使用模板工具类的好处。和前面讲解的一样,?是占位符,以便在后面的代码中设置问号处的参数,每个问号都一次对应一个索引值,从序号1开始。在设置语句中,如果直接用ps.setDate(2, birthday)将会出错,因为ps中setDate方法接受的参数的sql包中的类型,而birthday是util包中的类型。所以要经过上面的类型转换才能正确插入数据库中。这是写入数据库时的转换,从数据库读出时,可以不转换,因为两个日期类型是父子关系,所以父类的引用的可以指向子类的对象。下面两句用在读出操作里面都是可以的:
birthday = new Date(rs.getDate("birthday").getTime());
birthday = rs.getDate("birthday");
但是上面两句虽然都能通过编译,输出的结果还是有差别的。第二句输出的是只有日期的Date,第一句带有标准的时间。除了日期类型,其它基础类型都比较简单,所以接下来要讲解的是两个感觉上复杂一些的类型。在mysql中,默认情况下varchar类型是45个字符的长度,但是最多也只能容纳255个字符,所以不适合存储大量的文本信息。解决方式是用CLOB 类型来表示。但是不同的数据库对这种类型的描述名称有差异,而且可能不只一种。在mysql中,其中的一种表示CLOB类型的名称是TEXT,视频中演示了该类型的使用,同时创建了一个辅助的表clob_test(id,big_text),其中id是整数,big_text是TEXT类型的。
static void create() throws SQLException, IOException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "insert into clob_test(big_text) values (?) ";
ps = conn.prepareStatement(sql);
File file = new File("src/JdbcUtils.java");
Reader reader = new BufferedReader(new FileReader(file));
ps.setCharacterStream(1, reader, (int) file.length());
int i = ps.executeUpdate();
reader.close();
System.out.println("i=" + i);
} finally {
JdbcUtils.free(rs, ps, conn);
}
}
}
上面的代码显示,与基本类型的主要差别就是文件的输入操作。通过JAVA IO操作,将文件包装成字符流封装在一个Reader对象中,然后调用ps的setCharacterStream方法输入字符流到数据库。setCharacterStream有三个参数,第一个是占位符的索引号,第二个就是字符流对象,最后一个就是文件的长度。如果能确定字符流中全是ASCII字符,则可以调用setAsciiStream方法来写入ASCII字符流到数据库中。了解了写入数据库中,另外一半就是从数据库中读出来。除了模板代码,关键的代码如下:
rs = st.executeQuery("select big_text from clob_test");
while (rs.next()) {
Clob clob = rs.getClob(1);
Reader reader = clob.getCharacterStream();
File file = new File("JdbUtils_bak.java");
Writer writer = new BufferedWriter(new FileWriter(file));
char[] buff = new char[1024];
for (int i = 0; (i = reader.read(buff)) > 0;) {
writer.write(buff, 0, i);
}
writer.close();
reader.close();
}
一个是执行语句是最简单的查询语句,然后就是结果集的处理。先根据查询的列从结果集中得到Clob对象,然后通过IO操作将数据写入到硬盘上。当然实际中也可以写入到其他地方,上例只是演示了如何获取大的文本对象。