ORMapping
以数据库的连接为例,大体可分为三个层次
这是我们要实现的接口判断登录账号密码是否正确
public interface ILogin {
public boolean login(String userid, String password);
}
第一层
其实没有什么好说的,看一下代码就清楚了
- 数据库的连接
public static final String JDBCDRIVER ="com.mysql.jdbc.Driver";
private static final String JDBCURL="jdbc:mysql://localhost:3306/db_test?useUnicode=true&characterEncoding=utf-8";
private static final String DBUSER="root";
private static final String DBPWD="root";
static{
try {
Class.forName(JDBCDRIVER);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection() throws java.sql.SQLException{
return java.sql.DriverManager.getConnection(JDBCURL, DBUSER, DBPWD);
}
- 查询语句的书写和执行
public class LoginServiceV1 implements ILogin{
@Override
public boolean login(String userid, String password) {
Connection conn=null;
try {
conn = DataBaseManager.getConnection();
String sql="select password" +
" from user_t" +
" where userid = '" + userid + "'";
System.out.println("LoginServiceV1 SQL:" + sql);
java.sql.PreparedStatement pst=conn.prepareStatement(sql);
java.sql.ResultSet rs=pst.executeQuery();
if(rs.next()){
String pwd = rs.getString(1);
if(pwd.equals(password)){
return true;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
finally{
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return false;
}
}
第二层
其实也很简单,我新建了一个Dao层,将所有关于数据库的基本操作放入了Dao层,同时将javaBean或者说POJO作为查询结果的返回对象
- 首先我定义了一个Bean,其中get,set方法我就不写了,eclipse和idea都有可以快速添加的方法
public class User {
private String userid;
private String name;
private String password;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- 然后我又写了一个Dao层的方法
public class UserDao {
public User getUser(String userid){
Connection conn=null;
User user = new User();
try {
conn = DataBaseManager.getConnection();
String sql="select userid, name, password" +
" from user_t" +
" where userid = '" + userid + "'";
java.sql.PreparedStatement pst=conn.prepareStatement(sql);
java.sql.ResultSet rs=pst.executeQuery();
if(rs.next()){
String name = rs.getString(2);
String pwd = rs.getString(3);
user.setName(name);
user.setUserid(userid);
user.setPassword(pwd);
}
} catch (SQLException e) {
e.printStackTrace();
}
finally{
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return user;
}
}
看到了吗,返回值从一个Boolean转换成了一个User对象,这意味着当我们有其他的业务操作(比如说根据用户userid获取username)我可以直接使用当前的方法,不必重新再写一个类似的方法
3. 再来看看业务这边,一下子简洁多了,同时也不用管数据存在哪里(MySQL,Oracle,hbase等),也不用管数据库表是如何设计的了,一句话数据库我不用管了
public class LoginServiceV2 implements ILogin{
@Override
public boolean login(String userid, String password) {
UserDao dao = new UserDao();
User user = dao.getUser(userid);
if(user != null && user.getPassword().equals(password)){
return true;
}
return false;
}
}
第三层
先总结一下第三层干了什么事。1、将数据库信息放到的配置文件里面。2、通过泛型,映射存储任意对象
- 我们首先看看业务这边
public class LoginServiceV3 implements ILogin{
@Override
public boolean login(String userid, String password) {
IDao<User> dao = new Dao<>();
User user = dao.getEntity(userid, User.class.getName());
if(user != null && user.getPassword().equals(password)){
return true;
}
return false;
}
}
区别不是很大,仅仅是将Dao层的定义改了一下,加上了泛型的设计,同时添加了Dao层的接口。
2. 再看看Dao层的接口
public interface IDao<T> {
/**
* 通过key获取对象
* @param key 不一定是主键,可以是其他的column,只需要更改SQL语句即可
* @param clazz 对象类型
* @return 对象实体
*/
public T getEntity(String key, String clazz);
}
好吧这并看不出来什么,只能知道我可以通过key和javaBean的类名classname来获得一个对象,在结合业务代码我们可以知道这个对象就是我们需要的user对象,classname也就是User.class.getName()
3. Dao层方法的实现
再让我们理一下从数据库中查询的方法
- 首先我们需要连接数据库
- 其次我们需要一个SQL语句
- 查询语句的执行
- 最后将结果放到Bean里面去
这一层次中最关键的一部分来了
javaBean的配置文件
javaBean的配置文件
javaBean的配置文件
重要的东西说三遍,顺便加粗了一下
orm.properties
#Beanuser的配置信息
table.name=user_t
table.key=userid
table.col.1=userid
table.col.2=name
table.col.3=password
为了简单我用properties的配置文件,当然xml文件也是可以的spring,mybits那边就用的是xml的配置文件
现在再来看一下Dao层的实现方法
public class Dao<T> implements IDao<T> {
@Override
public T getEntity(String key, String clazz) {
List<String> cols = new ArrayList<String>();
String sql = createSQLFromConfig("orm.properties", key, cols);
Connection conn=null;
T entity = null;
try {
conn = DataBaseManager.getConnection();
java.sql.PreparedStatement pst=conn.prepareStatement(sql);
java.sql.ResultSet rs=pst.executeQuery();
if(rs.next()){
try {
entity = (T)Class.forName(clazz).newInstance();
//这里我们创建了一个实体对象
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (String col:cols) {
String value = rs.getString(col);
String setMethod = "set" + col.toUpperCase().substring(0, 1) + col.substring(1);
try {
entity.getClass().getMethod(setMethod, String.class).invoke(entity, value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return entity;
} catch (SQLException e) {
e.printStackTrace();
}
finally{
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
简单扫一眼,好像没有什么太大的区别,只是发现User这个对象不见了,这就是意味着不仅仅是User,其他的Bean也可以通过这个方法来获取整个对象的信息。
再仔细看看发现有这样的两句
entity = (T)Class.forName(clazz).newInstance();
entity.getClass().getMethod(setMethod, String.class).invoke(entity, value);
- 第一句的意思是创建一个对象并将它的类型设置成clazz,在当前环境下就是创建一个BeanUser的对象。对应第二层中的代码是User user = new User();
- 第二句,通过java的反射机制将查询出来的结果放入entity里面。setMethod是set方法的名称,举例:setUserid,setName,setPassword。String.class是set方法的参数类型,在这里也可以说是userid,userid,pwd的类型。value是对应参数的具体值。对应第二层的代码是user.setName(name);
user.setUserid(userid);
user.setPassword(pwd);
整个过程下来好像还少了点东西,先上代码
private String createSQLFromConfig(String config, String key, List<String> cols){
Properties properties = new Properties();
URL url = this.getClass().getClassLoader().getResource(config);
try {
properties.load(new FileReader(new File(url.getPath())));
Enumeration<String> tokens = (Enumeration<String>)properties.propertyNames();
String table = "";
String pk = "";
while(tokens.hasMoreElements()){
String name = tokens.nextElement();
if(name.startsWith("table.key")){
pk = properties.getProperty(name);
} else if (name.startsWith("table.name")){
table = properties.getProperty(name);
} else if(name.startsWith("table.col")){
cols.add(properties.getProperty(name));
}
}
String sql = "select 1";
for (String col:cols) {
sql = sql + "," + col;
}
sql = sql + " from " + table + " where " + pk + "='" + key + "'";
return sql;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
仔细看看,就会发现我读了一个文件,将里面的数据拿出来拼了一个用table.key来获取所有信息的SQL语句,同时将所有的列名column放入到了cols的List里面。
到这里整个过程结束了。从第一层到第三次完成了从单一的对比到获取单一的对象再到获取任意对象。
补充
- 首先使用过spring,mybits的人就会发现SQL语句是我写到配置文件里面去了,而并非系统自动拼接出来的,由于SQL语句的多样化而产生了两种思维,一种是hibernate,所有的SQL语句我都帮你实现好,另一种是spring,SQL语句你自己设计,我帮你把其他的实现好。如果将SQL语句放到配置文件也是可以的,只需要将createSQLFromConfig方法中SQL语句的拼接改成从文件中直接读取即可
- 当前这个案例有一个问题不知道有没有发现。如果Bean当中的域名,变量名与数据库中不一致会报错,说没有这个方法。关于这一点的话可以看看setMethod这个字符串是怎么拼接出来的,在本案例中为了简洁我就直接使用column的名称来拼接的。如果想通过Bean中的变量名来拼接也是可以的,我在下面一点中讲到
- 在java中有这样一个方法
Field[] ef = entity.getClass().getDeclaredFields();
我可以通过对象去获得所有的域(userid,username,password),再通过域来获取变量的名称field.getName(),以及类型field.getClass()。这是通过java反射机制完成的。所以只能实现java的基础类型的映射,有兴趣的话可以深入了解一下。
4. 自定义类型映射,在配置文件中加入对应属性的类型,再调用set方法的时候用if判断一下就行。
5. 其实连接数据库的信息
private static final String JDBCDRIVER ="com.mysql.jdbc.Driver";
private static final String JDBCURL="jdbc:mysql://localhost:3306/db_test?useUnicode=true&characterEncoding=utf-8";
private static final String DBUSER="root";
private static final String DBPWD="root";
这些也可以放到配置文件中。