对于要使用数据库的应用来说,一般会由数据库的开发人员提供一套封装好的 API而非直接操作数据库,这样上层应用便不太需要关心如何进行数据的存取等细节问题,更好的代码的封装有利于提高其重用性、可维护性等。当然,对此问题 Hibernate[hbnt] 已提供了足够的支持,它提供分页 [hbpg] 、缓存机制 [hbch] 。但若由于各种原因,不能或不想使用 hibernate的话,可自己提供一套。
1 Hibernate
先简单了解下 hibernate做的工作,那么自己要写的接口也是类似的。
Hibernate会根据表的字段,生成一个数据库表到 Java对象的映射文件,据此映射文件再生成对应的 Java类及 DAO( Data Access Objects,数据访问对象)。
对于一个简单 Student表,生成的映射文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="examble.hibernatemap.Student" table="STUDENT" schema="COLLEGE">
<id name=" id" type="java.lang.String">
<column name="ID" length="40" />
<generator class="assigned" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" length="40" not-null="true" />
</property>
<set name="courses" inverse="true">
<key>
<column name="SID" length="40" />
</key>
<one-to-many class="sample.hibernatemap.StudentCourse" />
</set>
</class>
</hibernate-mapping>
注:
如果还存在一对多 /多对多关系的话,如一个学生可选择多个课程, Student跟 StudentCourse存在一对多关系,则在 property元素之后还会有一个 set元素,如上面 xml文件中的灰色阴影部分。
DAO的接口如下(增加了一个简单的测试),仍对应数据库中的增删查改:
public class StudentDAO {
//添加一个学生
public static boolean save(Student aStudent) {…}
//添加一个学生
public static boolean update(Student aStudent) {…}
//删除一个学生
public static boolean deleteById(String id) {…}
//获取所有学生
public static List<Student> findAll() {..}
//获取某个学生
public static Student findById(String id) {…}
//根据某个属性获取学生
public static List< Student > findByField(String fieldname, String fieldvalue) {
//根据特定查询语句获取学生
public static List< Student > findBySql(String sqlStr) {…}
//一个简单的测试
public static void main(String[] args) {
//增加一名学生
Student student = new Student(“001”, “Zhang San”);
boolean resident = StudentDAO.save(student);
System.out.println(“save: ” + result);
}
}
下面是一个实际的例子:
2 代码 1 ,数据库操作:(这里是 Oracle数据库)
package ling.sample.db;
import java.sql.*;
import javax.sql.*;
import java.util.UUID;
import java.io.*;
import java.util.Properties;
//DataSource
import oracle.jdbc.pool.*;
import javax.naming.Context;
import javax.naming.InitialContext;
public class DB
{
//格式为 "jdbc:oracle:thin:@ip:port:dbName";
private static String url = "jdbc:oracle:thin:@192.168.0.110:1521:myoracle";
private static String userName = "myoracle";
private static String password = "myoracle";
public static final String ORACLE_DRIVER = "oracle.jdbc.driver.OracleDriver";
public static Connection createConection()
{
try
{
//加载 Driver
Class.forName(DB.ORACLE_DRIVER);
Connection connection = DriverManager.getConnection(url, userName, password);
return connection;
}
catch(Exception ex)
{
System.out.println(ex.getMessage() );
}
return null;
}
public static void closeConnecion(Connection connection)
{
if(connection != null)
{
try
{
connection.close();
}
catch(Exception ex)
{
System.out.println(ex.getMessage() );
}
}
}
public static void closeStatement(Statement statement)
{
if(statement != null)
{
try
{
statement.close();
}
catch(Exception ex)
{
}
}
}
public static void closeResultSet(ResultSet resultSet)
{
if(resultSet != null)
{
try
{
resultSet.close();
}
catch(Exception ex)
{
System.out.println(ex.getMessage() );
}
}
}
public static ResultSet executeQuery(Connection connection, String sql)
throws SQLException
{
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
return resultSet;
}
public static ResultSet executeQuery(String sql)
{
Connection connection = DB.createConection();
ResultSet resultSet = null;
try
{
resultSet = DB.executeQuery(connection, sql);
}
catch(SQLException ex)
{
}
finally
{
DB.closeConnecion(connection);
}
return resultSet;
}
public static boolean executeUpdate(Connection connection, String sql)
{
int result = 0;
Statement statement = null;
try
{
statement = connection.createStatement();
result = statement.executeUpdate(sql);
}
catch(Exception ex)
{
}
finally
{
DB.closeStatement(statement);
}
return result > 0;
}
public static boolean executeUpdate(String sql)
{
Connection connection = DB.createConection();
if(connection == null)
{
return false;
}
try
{
return DB.executeUpdate(connection, sql);
}
catch(Exception ex)
{
}
finally
{
DB.closeConnecion(connection);
}
return false;
}
/**
* 生成一个唯一的 id
*/
public static String createUUID()
{
return UUID.randomUUID().toString();
}
/**
* 使用 DataSource接口创建一个连接。
* 此种方式移植性更好,只需要在本机配置数据库的数据源即可。
* @return
*/
public static Connection createConnectionFromDS()
{
try
{
OracleDataSource oracelDataSource = new OracleDataSource();
oracelDataSource.setServerName("SERVER-CENTER");
//oracelDataSource.setURL("202.117.118.45");
oracelDataSource.setDatabaseName("myoracle");
Context context = new InitialContext();
context.bind("jdbc/repace", oracelDataSource);
DataSource dataSource = (DataSource) context.lookup("jdbc/myoracle");
Connection connection = dataSource.getConnection("myoracle", "myoracle");
return connection;
}
catch(Exception ex)
{
}
return null;
}
public static void main(String[] args)
{
Connection connection = DB.createConection();
//Connection connection = DB.createConnectionFromDS();
if(connection != null)
{
System.out.println("Connect to oralce successfully!");
}
else
{
System.out.println("Faliled to connect to oralce!");
}
}
}
为简单起见,这里所有的数据使用的是 static的 string私有数据及方法,这样使用时可以不必保存 DB对象。
也可以将数据库的配置数据写在一个属性文件中(如何使用 java读取 /设置属性文件参考,如何读取环境变量参考 http://ling091.javaeye.com/admin/blogs/354052)。
文件中的值如下:
IP= 192.168.0.110
port=1521
serviceName=myoracle
userName=myoracle
password=myoracle
代码如下(需要包含头文件 java.util.Properties,如用 Eclipse 可以使用 Ctrl-Shift-O 自动包含未包含的头文件, Mac上使用 Cmd-Shift-O ):
private static String[] readProperties()
{
String propFile = DB.PROP_FILE;
//根目录保存在环境变量中
String muleHome = System.getenv("ORACLE_EXAMPLE_HOME" );
if (dirHome != null )
{
propFile = dirHome + "//userconf//" + propFile;
}
// 如果不存在配置文件
/*File file = new File(propFile);
if(!file.exists() )
{
System.out.println(" 找不到配置文件 oracel.conf ,使用程序默认配置 !");
return null;
}*/
String[] properties = new String[5];
String[] propNames = {"IP" , "port" , "serviceName" , "userName" , "password" };
Properties props = new Properties();
InputStream istream = null ;
try
{
istream = new BufferedInputStream (new FileInputStream(propFile) );
props.load(istream);
for (int i = 0; i < propNames.length; i++)
{
properties[i] = props.getProperty (propNames[i]);
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
istream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
return properties;
}
3 代码 2 : Student类 略
4 代码 3 : StudentDAO
对于有一对多,多对多的对象, DAO的实现可根据需求而定。可以每张数据表 (如 student表, course表 ) 单独实现 DAO, 然后为关联表( student_course表)实现表征 student与 course关系的 DAO,这意味着为每张表实现一个 DAO。但如果两者关系更为密切,如表 1与表 2是一对多的关系,在类的层面上,表 2更合适作为表 1内部属性,可以将直接定义成一个对象。如联系人也可以放在学生的属性中。
这需要根据需要权衡,如同在数据库中将属性用另外的表存储还是作为已有表的属性存储, xml中作为 attribute还是 element是类似的。
package ling.example.oracle.dao;
import java.sql.*;
import javax.sql.*;
import java.util.*;
import example.oracle.db.*;
import example.oracle.data.*;
public class StudentDAO
{
public static boolean save(Student student)
{
String sql = "insert into student(id, name) values(" +
+ "'" + student.getID() + "',"
+ "'" + student.getName() + "')";
DB.executeUpdate(sql);
}
public static boolean update(Student student)
{
String sql = "update student set name = '"
+ student.getName()
+ "' where id = '"
+ student.getID()
+ "'";
return DB.executeUpdate(sql);
}
public static boolean deleteById(String id)
{
String sql = "delete from student where id = '" + id + "'";
return DB.executeUpdate(sql);
}
public static boolean deleteAll()
{
String sql = "delete from student”;
return DB.executeUpdate(sql);
}
//删除满足 query查询的学生
public static boolean deleteByQuery(String querySQL)
{
String sql = "delete from student where id in (" + querySQL + “)”;
return DB.executeUpdate(sql);
}
public static List<Student> findBySQL(String sql)
{
Connection connection = DB.createConection();
Statement statement = null;
ResultSet resultSet = null;
if(connection == null)
{
return null;
}
try
{
//ResultSet resultSet = DB.executeQuery(connection, sql);
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
List<Student> studentList = new ArrayList<Student>();
while(resultSet.next() )
{
Student student = new Student(
resultSet.getString(1),
resultSet.getString(2) );
studentList.add(student);
}
return studentList;
}
catch(Exception ex)
{
}
finally
{
DB.closeResultSet(resultSet);
DB.closeStatement(statement);
DB.closeConnecion(connection);
}
return null;
}
//如果有多个属性时,可以使用该查询
public static List<Student> findByFiled(String fieldName, String fieldValue)
{
String sql = "select id, name from student where "
+ fieldName
+ " = '"
+ fieldValue
+ "'";
return findBySQL(sql);
}
public static Student findById(String studentid)
{
List<Student> studentList = findStudentByFiled("studentid", studentid);
if(students != null && students.length > 0)
{
return studentList[0];
}
return null;
}
public static List<Student> findAll ()
{
String sql = "select id, name, password from student";
return findBySQL(sql);
}
public static void main(String[] args)
{
boolean result = false;
//--------save----------
Student student = new Student( "001", "zhang san");
result = StudentDAO.saveStudent(student);
System.out.println("save: " + result);
}
}
如果有多条语句同时执行,可以使用基本的数据库操作进行:
//这里是一个复杂插入的例子,比如一个学生有多个联系人方式
//这里使用基本的数据库操作。
public static boolean saveStudent(Student student)
{
Connection connection = DB.createConection();
PreparedStatement pstatement = null ;
PreparedStatement pstatement2 = null ;
PreparedStatement pstatement3 = null ;
if (connection == null )
{
return false ;
}
try
{
connection.setAutoCommit( false );
String sql = "insert into student(id, name) values (?, ?)" ;
pstatement = connection.prepareStatement(sql);
String studentid = student.getID();
pstatement.setString(1, studentid);
pstatement.setString(2, student.getName() );
int result = pstatement.executeUpdate();
//插入联系人
ContractPerson[] contractPersons = student.getContractPersons();
if (contractPersons != null && contractPersons.length > 0)
{
sql = "insert into contractPerson(id, name, phone_number) values(?, ?, ?)" ;
String sql3 = "insert into student_contractPerson(id, contract_id) values(?, ?)" ;
pstatement2 = connection.prepareStatement(sql);
pstatement3 = connection.prepareStatement(sql3);
int i = 0;
int count = roles.length;
for (; i < count; ++i)
{
String contractid = DB.createUUID();
pstatement2.setString(1, );
pstatement2.setString(2, contractPersons[i].getName() );
pstatement2.setString(3, contractPersons[i].getPhoneNumber() );
pstatement2.executeUpdate();
pstatement2.clearParameters();
pstatement3.setString(1, studentid);
pstatement3.setString(2, contractid);
// 先执行语句 2 ,再执行语句 3
pstatement3.executeUpdate();
pstatement3.clearParameters();
}
}
connection.commit();
return true ;
}
catch (SQLException ex)
{
try
{
connection.rollback();
}
catch (Exception ex)
{
System.out.println(ex.getMessage() );
}
}
finally
{
DB.closeStatement(pstatement);
DB.closeStatement(pstatement2);
DB.closeStatement(pstatement3);
DB.closeConnecion(connection);
}
return false ;
}
5 参考
[hbpg] Hibernate实现分页查询的原理分析 . http://www.javaeye.com/topic/261. 2003.09 .
如果相应的数据库定义了限定查询记录的 sql语句,那么直接使用特定数据库的 sql语句。如果数据库不支持分页的 SQL语句,那么根据在配置文件里面
#hibernate.jdbc.use_scrollable_resultset true
默认是 true,如果你不指定为 false,那么 Hibernate会使用 JDBC2.0的 scrollable result来实现分 页。 如果支持 scrollable result,使用 ResultSet的 absolute方法直接移到查询起点,如果不支持的话,使用循环语句, rs.next一点点的移过去。
可见使用 Hibernate,在进行查询分页的操作上,是具有非常大的灵活性, Hibernate会首先尝试用特定数据库的分页 sql,如果没用,再尝试 Scrollable,如果不行,最后采用 rset.next()移动的办法。
在查询分页代码中使用 Hibernate的一大好处是,既兼顾了查询分页的性能,同时又保证了代码在不同的数据库之间的可移植性。
[hbch] Hibernate 缓存机制 . http://www.360doc.com/content/10/0407/10/1158044_21911705.shtml. 2010.04
[dahb] 一个通用的 DAO接口及 Hibernate 实现 . http://jayjunyu.javaeye.com/blog/215838. 2008.07.
[hbqp] Hibernate 对 Dao层的处理实例 . 有模糊查询和 分页 . http://hi.baidu.com/xiaoxiaolq/blog/item/50c5961839488ab14bedbc70.html.2008.05.