MVC - pojo和底层DAO
Pojo编写
pojo即值对象,用于单纯地存储某个值或某些值所构建的对象,以存储用户信息为例,则编写一个`UserDetial`类,在其中设定好需要存储的每个用户信息,并为此类添加有参、无参构造方法、getter和setter,为了以后调试便利,顺便重写`toString()`方法:
public class UserDetail {
private Integer id;
private String name;
private Integer age;
private Integer height;
public UserDetail() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
@Override
public String toString() {
return "UserDetail{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
工具类编写
后期为了保证事务的原子性,将引入OpenSessionInViewFilter,所以需要将数据库的连接对象置于一个公共的、都可访问的共享环境中,方便起见,编写两个工具类:
- ConnUtil工具类,负责从ThreadLocal获取conn或直接生成conn并将之放入ThreadLocal,对象返回给2类使用;另外提供一个conn的关闭方法,避免2类直接操作conn对象,保证封装
public class ConnUtil extends ThreadLocal{ //设定数据库连接参数 private static String name = "root"; private static String pwd = "******"; private static String url = "jdbc:mysql://localhost:3306/demo"; private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); //负责传递connection的ThreadLocal private static Connection createConn() { //仅负责新建连接 try { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection(url, name, pwd); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } return null; } public static Connection getConn() { //在获取前判断一下是否存在,若不存在则先调用新建连接方法 Connection conn = threadLocal.get(); if(conn == null) { threadLocal.set(createConn()); conn = threadLocal.get(); } return conn; } public static void close() { //关闭connection,并去除ThreadLocal Connection conn = threadLocal.get(); if(conn == null) { return; } try { if(!conn.isClosed()) { conn.close(); threadLocal.remove(); } } catch (SQLException e) { e.printStackTrace(); } } } - TransactionManager类,提供“开始事务”、“提交事务”、“回滚事务”三个方法,调用1类,直接使用其返回值
当前主要任务是编写DAO,这个类将在之后划分事务时编写
BaseDAO编写
BaseDAO这个类的直接目的就是提供一个以方法为基础的SQL语句执行通道,其他DAO直接调用此类,就可以便捷地实现对数据库基本的增删改查
对这个类的相关信息进行规划:
- 为了保证此类的泛用性,考虑将之设定为泛型类;
- 前面已经编写工具类ConnUtil,因此与数据库的连接直接从ThreadLocal中取;(这一步实际上完成了部分事务管理的工作)
- 执行SQL命令考虑使用PreparedStatement,以便进行搜索条件的装填和替换;
- 最后,为了在返回对象时,能够通过反射获取正确的对象和属性信息,有必要在构造函数中先获取自己当前的泛型名称
BaseDAO代码:
public class BaseDAO<T> {
private Connection connection = null;
private Class superClass;
private PreparedStatement ps;
public BaseDAO() {
//genericType其实是BaseDAO的Class
Type genericType = getClass().getGenericSuperclass();
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
Type actualType = actualTypeArguments[0];
try {
superClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private boolean isDefaultType(String typeName) { //分辨是否是自己编写的类型
return "java.lang.String".equals(typeName)
|| "java.lang.Integer".equals(typeName)
|| "java.util.Date".equals(typeName)
|| "java.sql.Date".equals(typeName);
}
private boolean isNotDefaultType(String typeName) {
return !isDefaultType(typeName);
}
//装填PreparedStatement的参数,以替换“?”
private void setParams(PreparedStatement ps, Object... params) throws SQLException {
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++)
ps.setObject(i + 1, params[i]);
}
}
//根据传来的数据装填该对象的属性信息
private void setValues(Object Obj, String name, Object value) {
Field field = null; //通过提供的属性名,拿到传入的目标对象的内部属性
try {
field = Obj.getClass().getDeclaredField(name);
String typeName = field.getType().getName(); //获取该署名类型名称,看是不是自己写的类
if (isNotDefaultType(typeName)) { //是自己写的类
//创建一个此类实例
value = Class.forName(typeName).getDeclaredConstructor().newInstance();
}
//将对象赋值给属性
field.setAccessible(true);
field.set(Obj,value);
} catch (NoSuchFieldException | ClassNotFoundException | InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public int update(String query, Object... params) throws SQLException {
connection = ConnUtil.getConn();
ps = connection.prepareStatement(query);
setParams(ps, params);
return ps.executeUpdate();
}
public T getOneObj(String query, Object... params) {
connection = ConnUtil.getConn();
ResultSet resultSet;
try {
ps = connection.prepareStatement(query);
setParams(ps, params);
resultSet = ps.executeQuery(); //获取结果
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); //得到MetaData,用于得到列数和获取列名
int len = resultSetMetaData.getColumnCount();
if (resultSet.next()) {
T Obj = (T) superClass.getConstructor().newInstance(); //实例化一个T
for(int i=0;i<len;i++) {
//往T里塞值
setValues(Obj,resultSetMetaData.getColumnName(i+1),resultSet.getObject(i+1));
}
return Obj;
}
} catch (SQLException | NoSuchMethodException | IllegalAccessException | InvocationTargetException |
InstantiationException | ClassNotFoundException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
//得到的多个结果封装为对象列表
public List<T> getSeriesObj(String query, Object... params) {
connection = ConnUtil.getConn();
ResultSet resultSet;
List<T> res = new ArrayList<T>();
try {
ps = connection.prepareStatement(query);
setParams(ps,params);
resultSet = ps.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); //得到MetaData,用于得到列数和获取列名
int len = resultSetMetaData.getColumnCount();
while (resultSet.next()) {
T Obj = (T) superClass.getConstructor().newInstance(); //实例化一个T
for(int i=0;i<len;i++) {
//往T里塞值
setValues(Obj,resultSetMetaData.getColumnName(i+1),resultSet.getObject(i+1));
}
res.add(Obj);
}
} catch (SQLException | NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) {
e.printStackTrace();
}
return res;
}
}
DAO接口和实现类编写
完成了BaseDAO的编写后,就要进行DAO接口及其实现类的编写,注意需要保证DAO接口方法都是细粒度方法,即一个方法仅完成一个面向实际对象的操作,例如根据id删除某用户的操作
DAO接口代码:
public interface UserDetailDAO {
void addUser(UserDetail userDetail); //增加用户
UserDetail getUser(int id); //根据id获取用户对象
void delUser(int id); //根据id删除用户对象
List<UserDetail> getSeriesUser(String query, Object... params); //根据提供的条件获取多个用户
}
相应的,需要编写此接口的实现类,并通过BaseDAO的调用来实现接口中的方法:
public class UserDetailDAOImpl extends BaseDAO<UserDetail> implements UserDetailDAO {
@Override
public void addUser(UserDetail userDetail) {
try {
int res = super.update("insert into userbasic values(?,?,?,?)", userDetail.getId(), userDetail.getName(), userDetail.getAge(), userDetail.getHeight());
System.out.println("添加完成," + res + "行受影响");
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public UserDetail getUser(int id) {
return super.getOneObj("select * from userbasic where id=?", id);
}
@Override
public void delUser(int id) {
try {
int res = super.update("delete from userbasic where id=?", id);
System.out.println("删除完成," + res + "行受影响");
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public List<UserDetail> getSeriesUser(String query, Object... params) {
return super.getSeriesObj(query,params);
}
}
测试
编写测试类进行测试:
public class UserDetailDAOTest {
public static void main(String[] args) throws SQLException {
UserDetailDAOImpl userDAO = new UserDetailDAOImpl();
userDAO.addUser(new User(3,"fyc",20,180));
User user = userDAO.getUser(3);
System.out.println(user);
userDAO.delUser(3);
List<User> list = userDAO.getSeriesUser("select * from userbasic",null);
for(UserDetail u: list)
System.out.println(u);
}
}
在执行时遇到错误:

使用e.printStackTrace()后,发现问题出在setValues()方法的Class.forName()找不到对应的类,观察出错点,发现程序已经进入if语句之中,再通过打印出当前的typeName,进一步确定是类型名不能识别的原因
所以,错误原因是,在判断是否是自创类时,判断的依据有误,具体原因是:在原本的UserDetail类中,只有int类型属性,没有Integer类型属性,而int不在java.lang中,因此可以直接将pojo的int类型属性改为Integer,使之符合所指定的包装类,能被正确识别即可
最后,成功执行全部代码:


浙公网安备 33010602011771号