三层架构
所谓的三层:
- DAO层
- 也可以叫做Mapper或者是Repository层,名字无所谓,但是要清楚这层就是专门和数据库做交互的,一般指的是关系型数据库
- Service层
- 数据的加工处理,调动DAO层去完成一些具体的业务实现,包括事务的控制。
- Web层
- 现在还没有学习关于Web的知识,现在基本都是根据main方法或者是控制台输入来实现一些交互,到了后期,会通过前端的页面或者其他的方式和这层做基本交互。
- 手机用户的数据和需求,并且给用户返回需要展示的数据。
其次是关于包的命名
- dao:放和数据库交互的DAO层接口和实现类
- entity:放实体类,实体类是和数据库中的表做映射的。
- service:放做业务处理的service层的接口和实现类。
- utils:一般存放一些通用性的工具。
- web:后期会存放Servlet的东西,完成和页面之间的交互(现在还没学!!)
DAOUtils
在DAO层中,现在依然有大量的冗余代码在,比如获取连接,什么PreparedStatement等等的操作,都是重复性的,能不能再次封装一封,让DAO层的操作变的更简单
- 写操作:
- SQL语句是什么?
- 占位符要赋什么值?
- 读操作:
- SQL语句是什么?
- 占位符要赋什么值?
- 返回结果的封装?
封装写操作
声明一个DaoUtils的工具类,在这个工具类中完成写操作的封装。
package com.jimihua.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 将写操作和读操作再做一层封装!
*/
public class DaoUtils {
/**
* 公共处理增删改三个操作的方法
* @param sql SQL语句
* @param args 占位符要赋的值
* @return
*/
public static int commonUpdate(String sql,Object... args) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
int count = 0;
try {
//1、获取连接
conn = DatabaseUtils.getConnection();
//2、构建PreparedStatement对象
ps = conn.prepareStatement(sql);
//3、给占位符赋值
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4、执行SQL,获取返回结果
count = ps.executeUpdate();
}finally {
//5、释放资源
DatabaseUtils.closeAll(null,ps);
}
//6、响应返回结果
return count;
}
}
去优化PersonDao和AccountDao中的各种写操作进行优化,简化代码!
封装读操作
读操作需要考虑一下执行SQL语句之后,会拿到ResultSet,需要将Result封装对应实体类。
需要提供一个方法,这个方式可以基于泛型指定具体的类型,不同的实体类各自去重写方法实现即可。
因为之前DaoUtils的工具,提供了static方法,但是static方法不能和泛型一起用,为了解决,将之前的写操作的通用方法改造成非静态方法。
上述操作完成后,优先声明一个接口,RowMapper。
package com.jimihua.rowmapper; import java.sql.ResultSet; import java.sql.SQLException; /** * 向上抽取一个RowMapper接口,提供一个将resultSet封装为对应实体类的方法 * @param <T> */ public interface RowMapper<T> { /** * 这个方法需要不同的Dao,不同的实体类各自去实现即可。 * @param rs * @return * @throws SQLException */ T rowMapper(ResultSet rs) throws SQLException; }
在DaoUtils工具类中,声明对应的公共查询方法
package com.jimihua.utils; import com.jimihua.rowmapper.RowMapper; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * 将写操作和读操作再做一层封装! */ public class DaoUtils<T> { /** * 公共处理增删改三个操作的方法 * @param sql SQL语句 * @param args 占位符要赋的值 * @return */ public int commonUpdate(String sql,Object... args) throws SQLException { Connection conn = null; PreparedStatement ps = null; int count = 0; try { //1、获取连接 conn = DatabaseUtils.getConnection(); //2、构建PreparedStatement对象 ps = conn.prepareStatement(sql); //3、给占位符赋值 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } //4、执行SQL,获取返回结果 count = ps.executeUpdate(); }finally { //5、释放资源 DatabaseUtils.closeAll(null,ps); } //6、响应返回结果 return count; } public List<T> commonSelect(String sql, RowMapper<T> rowMapper,Object... args) throws SQLException { //1、获取连接 Connection conn = DatabaseUtils.getConnection(); //2、基于SQL构建PreparedStatement PreparedStatement ps = conn.prepareStatement(sql); //3、占位符赋值 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1,args[i]); } //4、执行SQL,拿到ResultSet结果集 ResultSet resultSet = ps.executeQuery(); //5、封装结果集 List<T> list = new ArrayList<T>(); while(resultSet.next()){ T t = rowMapper.rowMapper(resultSet); list.add(t); } //6、释放资源 DatabaseUtils.closeAll(conn,ps,resultSet); //7、响应返回结果 return list; } }
为了可以在具体的Dao中使用DaoUtils的公共查询方法,需要将RowMapper做对应的实现
PersonRowMapper:
package com.jimihua.rowmapper.impl; import com.jimihua.entity.Person; import com.jimihua.rowmapper.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; /** * 将ResultSet结果集封装为Person对象的方法 */ public class PersonRowMapper implements RowMapper<Person> { @Override public Person rowMapper(ResultSet resultSet) throws SQLException { Person person = new Person(); person.setId(resultSet.getInt("id")); person.setName(resultSet.getString("name")); person.setAge(resultSet.getInt("age")); person.setBornDate(resultSet.getDate("born_date")); person.setEmail(resultSet.getString("email")); person.setAddress(resultSet.getString("address")); return person; } }
AccountRowMapper:
package com.jimihua.rowmapper.impl; import com.jimihua.entity.Account; import com.jimihua.rowmapper.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; /** * 将ResultSet封装为Account对象的RowMapper */ public class AccountRowMapper implements RowMapper<Account> { @Override public Account rowMapper(ResultSet rs) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setCardNo(rs.getString("card_no")); account.setPwd(rs.getString("pwd")); account.setMoney(rs.getBigDecimal("money")); return account; } }
改造对应的DaoImpl实现的查询方法
PersonDaoImpl:
package com.jimihua.dao.impl; import com.jimihua.dao.PersonDao; import com.jimihua.entity.Person; import com.jimihua.rowmapper.impl.PersonRowMapper; import com.jimihua.utils.DaoUtils; import java.sql.SQLException; import java.util.List; /** * 实现PersonDao接口,实现内5个方法。 */ public class PersonDaoImpl implements PersonDao { private DaoUtils<Person> daoUtils = new DaoUtils(); // 省略写操作代码 @Override public Person findById(Integer id) throws Exception { //1、编写SQL语句 String sql = "select * from person where id = ?"; //2、调用DaoUtils List<Person> list = daoUtils.commonSelect(sql, new PersonRowMapper(), id); //3、返回结果 return list != null && list.size() > 0 ? list.get(0) : null; } @Override public List<Person> findAll() throws SQLException { //1、编写SQL语句 String sql = "select * from person"; //2、调用DaoUtils List<Person> list = daoUtils.commonSelect(sql, new PersonRowMapper()); //3、返回结果 return list; } }
AccountDaoImpl:
package com.jimihua.dao.impl; import com.jimihua.dao.AccountDao; import com.jimihua.entity.Account; import com.jimihua.rowmapper.impl.AccountRowMapper; import com.jimihua.utils.DaoUtils; import java.sql.SQLException; import java.util.List; public class AccountDaoImpl implements AccountDao { private DaoUtils<Account> daoUtils = new DaoUtils(); @Override public Account findByCardNo(String cardNo) throws SQLException { //1、编写SQL语句 String sql = "select * from account where card_no = ?"; //2、执行daoUtils查询 List<Account> list = daoUtils.commonSelect(sql, new AccountRowMapper(), cardNo); //8、返回结果 return list != null && list.size() > 0 ? list.get(0) : null; } // 省略修改操作 }
Druid连接池
在和数据库交互的时候,需要基于DriverManager去构建一个Connection对象,与数据库交互完毕后,还需要将这个Connection对象释放掉。这样 频繁的创建和释放Connection对象有点消耗资源。
连接池的池化技术就是来解决这个问题的。
Ps:其次连接池也可以更好的对Connection的操作做监控,以及根据项目的情况更好的去指定Connection的个数。
准备Druid连接池jar文件
依然是去mvnrepository.com中去下载对应的Druid连接池的jar文件
https://mvnrepository.com/artifact/com.alibaba/druid/1.2.15
下载完毕后,记得将jar包添加到工程中。
改造DatabaseUtils
之前获取连接对象,依然是通过DriverManager去get的,现在需要先初始化好 DruidDataSource 对象,并且设置好他需要的核心信息,后期再获取Connection时,要基于 DruidDataSource 去get到。
其次,在执行连接池提供的Connection的close方法时,会从原来的释放资源,变为归还给连接池,释放资源的代码不需要动!
初始化 DruidDataSource时,需要将连接数据库的几个信息交给 DruidDataSource ,最核心的是四个。
- 驱动类的全路径
- 连接数据库的url信息
- 用户名
- 密码
DatabaseUtils改造后的内容:
package com.jimihua.utils;
import com.alibaba.druid.pool.DruidDataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 数据库操作的一个通用的内容
*/
public class DatabaseUtils {
private static DruidDataSource dataSource = new DruidDataSource();
private static final Properties PROP = new Properties();
private static final ThreadLocal<Connection> tl = new ThreadLocal<>();
// 加载database.properties文件。 同时将信息设置给dataSource
static{
try {
// 优先通过PROP对象,加载database.properties文件
InputStream is = DatabaseUtils.class.getResourceAsStream("/database.properties");
PROP.load(is);
// 给dataSource设置四个核心信息
dataSource.setDriverClassName(PROP.getProperty("jdbc.driver"));
dataSource.setUrl(PROP.getProperty("jdbc.url"));
dataSource.setUsername(PROP.getProperty("jdbc.username"));
dataSource.setPassword(PROP.getProperty("jdbc.password"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取Connection对象方法
* @return
*/
public static Connection getConnection() {
//1、先尝试从ThreadLocal中获取。
Connection conn = tl.get();
if (conn == null) {
//2、没获取到,基于DriverManager获取Connection,然后添加到ThreadLocal中
try {
// 基于连接池获取Connection对象
conn = dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException("获取Connection出错!");
}
tl.set(conn);
}
//3、获取到了,直接返回
return conn;
}
// 省略部分功能代码
}
Apache的DBUtils
Commons DBUtils的工具,是Apache组织提供的一个对JDBC进行简单封装的开源工具类库。他可以简化咱们和数据库交互的操作,让更多的精力都放在编写SQL语句上。
DBUtils基本使用(增删改)
1、下载jar包。
2、将jar导入到项目当中。
3、需要给DatabaseUtils提供一个返回dataSource的静态方法。
4、可以在需要操作的DaoImpl类中,声明一个QueryRunner的类,传入dataSource。
5、完成基本的写操作。
下载jar包:直接去mvnrepository.com去下载。
https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils/1.8.1
将下载好的jar文件,添加到项目中………………
导入完毕后,测试一下,在任意类中编写QueryRunner得有提示…………
给DatabaseUtils提供返回DataSource的静态方法
-public class DatabaseUtils { private static DruidDataSource dataSource = new DruidDataSource(); public static DataSource getDataSource(){ return dataSource; } // 省略其他代码 }
根据数据库中仅存的user表,提供对应的UserDao以及UserDaoImpl。
在UserDaoImpl中去初始化QueryRunner,并传入DataSource
package com.jimihua.dao.impl; import com.jimihua.dao.UserDao; import com.jimihua.entity.User; import com.jimihua.utils.DatabaseUtils; import org.apache.commons.dbutils.QueryRunner; public class UserDaoImpl implements UserDao { // 初始化QueryRunner private QueryRunner queryRunner = new QueryRunner(DatabaseUtils.getDataSource()); @Override public int insert(User user) { return 0; } }
在这完成增删改操作。
package com.jimihua.dao.impl; import com.jimihua.dao.UserDao; import com.jimihua.entity.User; import com.jimihua.utils.DatabaseUtils; import org.apache.commons.dbutils.QueryRunner; import java.sql.SQLException; public class UserDaoImpl implements UserDao { private QueryRunner queryRunner = new QueryRunner(DatabaseUtils.getDataSource()); @Override public int insert(User user) throws SQLException { // 准备SQL String sql = "insert into user(username,password) values(?,?)"; // 基于QueryRunner执行SQL语句,传入占位符参数,获取结果 int count = queryRunner.update(sql, user.getUsername(), user.getPassword()); // 返回几行受影响 return count; } @Override public int updateById(User user) throws SQLException { String sql = "update user set username=?,password=? where id=?"; int count = queryRunner.update(sql, user.getUsername(), user.getPassword(), user.getId()); return count; } @Override public int deleteById(Long id) throws SQLException { String sql = "delete from user where id=?"; int count = queryRunner.update(sql, id); return count; } }
查询操作
查询操作相比写操作,只需要多关注一下返回的ResultSet结果集如何封装为对应的entity对象。
这个操作在DBUtils中,他已经提供对应的方式。
首先DBUtils的查询操作要调用QueryRunner的query方法执行SQL。
至于返回结果的封装,可以在query方法中基于传入不同Bean,可以让DBUtils做不同封装。
- BeanHandler:将一行返回结果,封装为一个对象。当前query方法返回一个entity实例。
- BeanListHandler:将多行返回结果,封装为一个集合。当前query方法返回一个List<entity>实例。
- ScalarHandler:只返回第一行第一列的一个结果。
直接完成针对UserDaoImpl中的查询操作
package com.jimihua.dao.impl; import com.jimihua.dao.UserDao; import com.jimihua.entity.User; import com.jimihua.utils.DatabaseUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.SQLException; import java.util.List; public class UserDaoImpl implements UserDao { private QueryRunner queryRunner = new QueryRunner(DatabaseUtils.getDataSource()); // 省略写操作代码 @Override public User selectById(Long id) throws SQLException { String sql = "select * from user where id = ?"; User user = queryRunner.query(sql, new BeanHandler<User>(User.class), id); return user; } @Override public List<User> selectAll() throws SQLException { String sql = "select * from user"; List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class)); return userList; } @Override public Long selectCount() throws SQLException { String sql = "select count(*) from user"; Long count = queryRunner.query(sql, new ScalarHandler<Long>()); return count; } }