Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上):https://developer.aliyun.com/article/1580591
8 Service 业务
8.1 什么是业务
代表用户完成的一个业务功能,可以由一个或多个DAO的调用组成。(软件所提供的一个功能都叫业务)
8.2 转账业务开发
转账业务分析 |
|
public class AccountServiceImpl implements AccountService { AccountDao accountDao = new AccountDaoImpl(); @Override public String zhuanZhang(String fromName, String password, String toName, double money) { try { //1、验证我方用户密码 Account account = accountDao.selectAccount(fromName); if(account == null){ return "用户名不存在"; } if(!account.getPassword().equals(password)){ return "用户密码不正确"; } //2、验证余额 if(account.getMoney() < money){ return "用户余额不足"; } //3、验证对方用户 if(accountDao.selectAccount(toName) == null){ return "对方用户名不存在"; } //4、我方扣钱 accountDao.updateAccount(fromName,-money); System.out.println(10/0); //5、对方加钱 accountDao.updateAccount(toName,money); return "转账成功"; } catch (SQLException throwables) { throwables.printStackTrace(); } return "转账失败"; } }
9 事务
在JDBC 中,获得 Connection 对象开始事务–提交或回滚–关闭连接。其事务操作是
- conn.setAutoCommit(false);//设置事务为手动提交
- conn.commit();//手动提交事务
- conn.rollback();//手动回滚事务
9.1 转账业务实现
public class AccountServiceImpl2 implements AccountService { AccountDao accountDao = new AccountDaoImpl(); @Override public String zhuanZhang(String fromName, String password, String toName, double money) { Connection conn = JDBCUtils.getConnection(); try { //1、验证我方用户密码 Account account = accountDao.selectAccount(fromName); if(account == null){ return "用户名不存在"; } if(!account.getPassword().equals(password)){ return "用户密码不正确"; } //2、验证余额 if(account.getMoney() < money){ return "用户余额不足"; } //3、验证对方用户 if(accountDao.selectAccount(toName) == null){ return "对方用户名不存在"; } //4、我方扣钱 accountDao.updateAccount(fromName,-money); //System.out.println(10/0); //5、对方加钱 accountDao.updateAccount(toName,money); //提交事务 conn.commit(); return "转账成功"; } catch (Exception throwables) { throwables.printStackTrace(); try { //回滚事务 conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } return "转账失败"; } }
注意:此时Service中的Connection与Dao中的Connection对象不一致,无法实现事务回滚
9.2 解决方案1:传递 Connection
- 如果使用传递Connection,容易造成接口污染(BadSmell)。
- 定义接口是为了更容易更换实现,而将 Connection定义在接口中,会造成污染当前接口。
9.3 解决方案2:ThreadLocal
- 可以将整个线程中(单线程)中,存储一个共享值。
- 线程拥有一个类似 Map 的属性,键值对结构。
9.4 使用ThreadLocal更新JDBC工具类
/** * ThreadLocal<T>:能保存对象,能保证在同一个线程下获取到的对象是同一个 * set(T); * get(); * remove(); */ static ThreadLocal<Connection> tl = new ThreadLocal<>(); public static Connection getConnection(){ //1、从ThreadLocal获取Connection //2、获取获取到了connection对象直接返回 //3、创建Connection对象并存到ThreadLocal中,在进行返回 Connection conn = tl.get(); try { if(conn == null){ conn = DriverManager.getConnection(url,username,password); tl.set(conn); } } catch (SQLException throwables) { throwables.printStackTrace(); } return conn; }
此时需要注意,如果关闭了Connetion连接,但是在ThreadLocal中还是保存着Connetion对象。下次会获取到一个已经关闭的Connection对象,所以需要从ThreadLocal中移除
9.5 事务封装
将事务的开启、提交、回滚都封装在工具类中,业务层调用即可。
//封装事务操作的三个方法 public static void begin(){ //1、获取Connection对象 Connection conn = getConnection(); try { //2、开启事务 conn.setAutoCommit(false); } catch (SQLException throwables) { throwables.printStackTrace(); } } public static void commit(){ //1、获取Connection对象 Connection conn = getConnection(); try { //2、提交事务 conn.commit(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { //3、关闭Connection资源 conn.close(); //从ThreadLocal中将connection移除掉 tl.remove(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } public static void rollback(){ //1、获取Connection对象 Connection conn = getConnection(); try { //2、回滚事务 conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { //3、关闭Connection资源 conn.close(); //从ThreadLocal中将connection移除掉 tl.remove(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
9.6 最终的转账业务
public class AccountServiceImpl2 implements AccountService { AccountDao accountDao = new AccountDaoImpl(); @Override public String zhuanZhang(String fromName, String password, String toName, double money) { try { //开启事务 JDBCUtils.begin(); //1、验证我方用户密码 Account account = accountDao.selectAccount(fromName); if(account == null){ return "用户名不存在"; } if(!account.getPassword().equals(password)){ return "用户密码不正确"; } //2、验证余额 if(account.getMoney() < money){ return "用户余额不足"; } //3、验证对方用户 if(accountDao.selectAccount(toName) == null){ return "对方用户名不存在"; } //4、我方扣钱 accountDao.updateAccount(fromName,-money); //System.out.println(10/0); //5、对方加钱 accountDao.updateAccount(toName,money); //提交事务 JDBCUtils.commit(); return "转账成功"; } catch (Exception throwables) { throwables.printStackTrace(); //回滚事务 JDBCUtils.rollback(); } return "转账失败"; } }
10 三层架构
三层架构原理 |
|
三层架构下包结构 |
|
11 单元测试
11.1 单元测试
/** * 单元测试:对已经编写完成的类、模块、方法进行测试 * 使用步骤: * 1、导入单元测试的两个jar包(与驱动包导入一致) * 2、编写方法进行测试 * 常用的注解: * @Test 单元测试的方法 * @Before 在单元测试方法之前执行 * @After 在单元测试方法之后执行 * @BeforeClass 在类加载之前执行 * @AfterClass 在类卸载之后执行 * * 单元测试需要注意的问题 * 1、@BeforeClass测试的方法必须要加static修饰 * 2、单元测试的方法不能有参数,不能有返回值 * 3、不能再单元测试中写Scanner输入内容 */
@BeforeClass public static void testBeforeClass(){ System.out.println("BeforeClass类加载的时候执行"); } @Before public void testBefore(){ System.out.println("Before在单元测试方法之前执行(自动执行)"); } @Test public void test01(){ System.out.println("单元测试"); } @After public void testAfter(){ System.out.println("After在单元测试方法之后执行(自动执行)"); } @AfterClass public static void testAfterClass(){ System.out.println("BeforeClass类卸载的时候执行"); }
执行结果
BeforeClass类加载的时候执行 Before在单元测试方法之前执行(自动执行) 单元测试 After在单元测试方法之后执行(自动执行) BeforeClass类卸载的时候执行
11.2 实际应用
在实际开发过程中,我们需要对写好的DAO层代码、Service层代码进行测试
一般就是对DAO、Service层中的每一个方法进行测试
public class AccountTest { @Test public void testZhuanZhang(){ AccountService accountService = new AccountServiceImpl2(); String s = accountService.zhuanZhang("jack","123","rose",200); System.out.println(s); } }
12 连接池
12.1 Druid连接池
在程序初始化时,预先创建指定数量的数据库连接对象存储在池中。当需要连接数据库时,从连接池中取出现有连接;使用完毕后,也不会进行关闭,而是放回池中,实现复用,节省资源。
- 创建 db.properties 配置文件。
- 引入druid的jar 文件,添加到类路径
12.2 db.properties
driverClass=com.mysql.jdbc.Driver url=jdbc:mysql:///java2303?useSSL=false username=root password=123456 #初始化连接 (初始化连接池的,里面默认就已经存在了20个Connection连接) initialSize=20 #最大连接数量 (当初始的20个连接不够的时候,最大会创建到50个) maxActive=50 #最小空闲连接 (当连接池中的连接,没有被使用,就会减少到5个) minIdle=5 #超时等待时间 (当连接数超过最大连接数,会等待5秒,如果5秒后还没有空闲连接,就会抛出异常) maxWait=5000
12.3 最终版JDBC工具类
DruidDataSourceFactory 导包com.alibaba.druid.pool
public class JDBCUtils { //定义数据库连接池 private static DataSource dataSource; //初始化连接池对象 static{ try { Properties properties = new Properties(); InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties"); properties.load(in); //初始化连接池对象 dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } //返回连接池对象 public static DataSource getDataSource(){ return dataSource; } //使用ThreadLocal保证Connection在同一个线程下唯一 static ThreadLocal<Connection> tl = new ThreadLocal<>(); public static Connection getConnection(){ Connection conn = tl.get(); try { if(conn == null){ conn = dataSource.getConnection(); tl.set(conn); } } catch (SQLException throwables) { throwables.printStackTrace(); } return conn; } public static void closeAll(Connection connection, Statement statement , ResultSet rs){ try { if(rs!=null) rs.close(); if(statement!=null) statement.close(); if(connection!=null) connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } public static void begin(){ Connection conn = getConnection(); try { conn.setAutoCommit(false); } catch (SQLException throwables) { throwables.printStackTrace(); } } public static void commit(){ Connection conn = getConnection(); try { conn.commit(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { conn.close(); tl.remove(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } public static void rollback(){ Connection conn = getConnection(); try { conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { try { conn.close(); tl.remove(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } }
12.4 连接池测试
public class TestDruidDataSource { public static void main(String[] args) throws SQLException { // Connection conn1 = JDBCUtils.getConnection(); // System.out.println(conn1); // Connection conn2 = JDBCUtils.getConnection(); // System.out.println(conn2); for (int i = 0; i < 51; i++) { Connection connection = JDBCUtils.getDataSource().getConnection(); System.out.println(connection); connection.close();//并不是关闭连接,而是归还到连接池中 } } }
14 DaoUtils工具类
将Dao层中增删改的代码进行封装
13.1 工具类实现
public class DaoUtils { //更新操作(增删改) insert into emp values(?,?,?,?,?,?,?,?) public static int commonsUpdate(String sql,Object... args) throws SQLException { //1、获取数据库连接对象 Connection conn = JDBCUtils.getConnection(); PreparedStatement ps = null; try { //2、获取数据库操作对象 ps = conn.prepareStatement(sql); //3、设置占位符的值 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //4、执行sql语句 int count = ps.executeUpdate(); //5、处理结果 return count; } finally { //6、关闭资源 JDBCUtils.closeAll(null,ps,null); } } public static <T> List<T> commonsQuery(String sql, Class c, Object... args) throws Exception { PreparedStatement ps = null; ResultSet rs = null; try { //1、获取数据库连接对象 Connection conn = JDBCUtils.getConnection(); //2、获取数据库操作对象 ps = conn.prepareStatement(sql); //3、设置占位符的值 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //4、执行SQL语句 rs = ps.executeQuery(); //5、处理结果集 List<T> list = new ArrayList<>(); while(rs.next()){ //通过类对象获取类的属性 Field[] fields = c.getDeclaredFields(); T obj = (T) c.newInstance(); for (int i = 0; i < fields.length; i++) { //暴力反射 fields[i].setAccessible(true); fields[i].set(obj,rs.getObject(i+1)); } //将对象装到List集合中 list.add(obj); } return list; }finally { JDBCUtils.closeAll(null,ps,rs); } } }
13.2 DaoUtils工具类使用
public class EmpDaoImpl implements EmpDao { @Override public int insertEmp(Emp emp) throws SQLException { String sql = "insert into emp values(?,?,?,?,?,?,?,?)"; Object[] args = {emp.getEmpno(),emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHireadate(),emp.getSal(),emp.getComm(),emp.getDeptno()}; return DaoUtils.commonsUpdate(sql,args); } @Override public int updateEmp(Emp emp) throws SQLException { String sql = "update emp set ename=?,job=?,mgr=?,hiredate=?,sal=?,comm=?,deptno=? where empno = ?"; Object[] args = {emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHireadate(),emp.getSal(),emp.getComm(),emp.getDeptno(),emp.getEmpno()}; return DaoUtils.commonsUpdate(sql,args); } @Override public int deleteEmp(int empno) throws SQLException { String sql = "delete from emp where empno = ?"; Object[] args = {empno}; return DaoUtils.commonsUpdate(sql,args); } @Override public List<Emp> selectAll() throws Exception { String sql = "select * from emp"; return DaoUtils.commonsQuery(sql,Emp.class); } }
14 DBUtils工具类
14.1 DBUtils简介
DbUtils是Java编程中数据库操作实用小工具,小巧、简单、实用
- 对于数据表的查询操作,可以把结果转换为List、Array、Set等集合。便于操作。
- 对于数据表的DML操作,也变得很简单(只需要写SQL语句)。
14.2 DbUtils核心API
ResultSetHandler接口:转换类型接口
- BeanHandler类:实现类,把一条记录转换成对象
- BeanListHandler类:实现类,把多条记录转换成List集合。
- ScalarHandler类:实现类,适合获取一行一列的数据。
- MapHandler类: 实现类,把一条记录转换成Map集合
- MapListHandler类:实现类,把多条记录转换成List集合。
- QueryRunner:执行sql语句的类
- 增、删、改:update();
- 查询:query();
14.3 DbUtils的使用步骤
导入jar包
- mysql连接驱动jar包
- 导入druid 的jar包
- database.properties配置文件
- 导入commons-dbutils的jar包
14.4 DBUtils使用
使用DBUtils实现增删改查
public class EmpDaoImpl implements EmpDao { @Override public int insertEmp(Emp emp) throws SQLException { //1、创建QueryRunner对象 //如果是更新(增删改)操作,那么就用无参的构造 QueryRunner qr = new QueryRunner(); //2、通过QueryRunner对象调用update String sql = "insert into emp values(?,?,?,?,?,?,?,?)"; Object[] args = {emp.getEmpno(),emp.getEname1(),emp.getJob(),emp.getMgr(),emp.getHireadate(),emp.getSal(),emp.getComm(),emp.getDeptno()}; return qr.update(JDBCUtils.getConnection(),sql,args); } @Override public int updateEmp(Emp emp) throws SQLException { //1、创建QueryRunner对象 //如果是更新(增删改)操作,那么就用无参的构造 QueryRunner qr = new QueryRunner(); //2、通过QueryRunner对象调用update String sql = "update emp set ename=?,job=?,mgr=?,hiredate=?,sal=?,comm=?,deptno=? where empno = ?"; Object[] args = {emp.getEname1(),emp.getJob(),emp.getMgr(),emp.getHireadate(),emp.getSal(),emp.getComm(),emp.getDeptno(),emp.getEmpno()}; return qr.update(JDBCUtils.getConnection(),sql,args); } @Override public int deleteEmp(int empno) throws SQLException { //1、创建QueryRunner对象 //如果是更新(增删改)操作,那么就用无参的构造 QueryRunner qr = new QueryRunner(); //2、通过QueryRunner对象调用update String sql = "delete from emp where empno = ?"; Object[] args = {empno}; return qr.update(JDBCUtils.getConnection(),sql,args); } @Override public List<Emp> selectAll() throws Exception { //1、创建QueryRunner对象 //如果是查询操作,那么就用有参的构造,传递连接池对象(使用完成之后QueryRunner会自动关闭(回收)) QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); //2、通过QueryRunner对象调用query String sql = "select empno,ename ename1,job,mgr,hiredate hireadate,sal,comm,deptno from emp"; //如果是集合就创建BeanListHandler对象,如果是实体类就创建BeanHandler对象 List<Emp> empList = qr.query(sql, new BeanListHandler<Emp>(Emp.class)); return empList; } @Override public Emp selectOne(int empno) throws SQLException { //1、创建QueryRunner对象 //如果是查询操作,那么就用有参的构造,传递连接池对象(使用完成之后QueryRunner会自动关闭(回收)) QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); //2、通过QueryRunner对象调用query String sql = "select * from emp where empno = ?"; Object[] args = {empno}; Emp emp = qr.query(sql, new BeanHandler<Emp>(Emp.class),args); return emp; } @Override public long count() throws SQLException { //1、创建QueryRunner对象 //如果是查询操作,那么就用有参的构造,传递连接池对象(使用完成之后QueryRunner会自动关闭(回收)) QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); String sql = "select count(*) from emp"; Long count = qr.query(sql, new ScalarHandler<Long>()); return count; } }
14.5 字段与属性名不一致
如果数据库的字段名与实体类中的属性名不一致,则无法完成映射,值会显示null
2种解决:
- 在查询语句中取别名
后期自定义映射
@Override public List<Emp> selectAll() throws Exception { //1、创建QueryRunner对象 //如果是查询操作,那么就用有参的构造,传递连接池对象(使用完成之后QueryRunner会自动关闭(回收)) QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); //2、通过QueryRunner对象调用query //通过别名进行数据映射 String sql = "select empno,ename ename1,job,mgr,hiredate hireadate,sal,comm,deptno from emp"; //如果是集合就创建BeanListHandler对象,如果是实体类就创建BeanHandler对象 List<Emp> empList = qr.query(sql, new BeanListHandler<Emp>(Emp.class)); return empList; }
public class Emp { private Integer empno; private String ename1; //这个与数据库字段不一致 private String job; private Integer mgr; private Date hireadate; //这个与数据库字段不一致 private Double sal; private Double comm; private Integer deptno; //省略set、get、构造方法 }
15 Lombok插件
Lombok简化编写类的构造方法、setget方法以及toString方法
使用步骤
安装插件,需要重启目前IDEA版本已经自带此插件无需安装 |
|
开启IEDA注解可用需要在setting中进行设置还需要在setting for newproject中设置 |
新版本使用lombok插件需要添加配置需要在setting中进行设置还需要在setting for newproject中设置 |
-Djps.track.ap.dependencies=false |
|
导入jar包 |
|
编写实体类 |
|