入门JAVA第十七天 Oracle的JDBC技术

一、数据库JDBC技术学习内容与方法

1.1 学习内容

(1) Oracle数据库

        目前最好的关系型数据库。

        基本的CRUD命令

        SQL语句。select(R),update(U),delete(D),insert(C)

(2) MySQL数据库

        中小型项目非常好用的关系型数据库。

        灵活,小巧。

(3)扩展软件开发流程中数据库设计原则

        ER图---》根据需求分析数据库结构。

        三范式---》学会使用范式来规范表结构

(4) JDBC

         在Java中连接数据库技术。

        在Java代码中想操作数据库中的数据,就必须使用到JDBC技术。

        提供JDBC从Java代码中想数据库发送执行的SQL语句

        工具类的封装。DAO-Service 的分层。

        一定要遵守编码规范

1.2 学习难点 

就是一套使用的规范。大家按规范编写就没有什么特别难理解的部分。

难点:

(1) 分层,理解DAO与Service层的区别。

(2) 封装工具类。连接对象需要单独封装起来。

       a 要使用连接池技术

       b ThreadLocal类。理解上需要大家想。

(3) 事务处理,异常处理,连接关闭。这三个内容大家必须清楚在哪处理。

1.3 学习方法

a 按规范比那些代码。

b 练习

二、JDBC是什么

JDBCJava DataBase Connector  中文:Java数据库连接技术

JDBC 是一种结束。一种在Java程序中连接数据库的技术。

使用JDBC可以实现在Java代码中去操作数据库表中的记录。

JDBC是一个持久化技术

三、持久化

化:过程。

数据是分为零食状态和持久状态。

        临时状态:数据保存在内存中。代码在运行时的数据。

        持久状态:数据保存在硬盘中,以文件的方式长期保存的数据。

持久化:数据在临时状态 与 持久状态 之间进行切换的过程。

        1 将Java程序中的数据保存到数据库中。

        2 将数据库职工的数据读取到Java程序中

四、JDBC是一套规范也是标准

JDBC是只一套规范,约定了Java语言连接到不同数据库产品时的标准

例如:USB接口。

任何数据库产品,想让自己的产品,可以使用JDBC连接到Java程序,就要按JDBC的标准编写数据库的驱动

数据库的驱动是数据库厂商按JDBC标准,由各数据库厂商自己编写的。

在Java程序中接口表示了一种能力,一种规范,一个标准,一种约定。

JDBC本身就是一组接口。各大数据库厂商需要根据自己的数据库去实现这一组接口。

五、JABC中的桑核心接口

5.1 Connection 接口

Connection是连接对象,负责创建Java程序与数据库之间的连接。

Connection接口的实现类由各数据库厂商提供。称为驱动类。操作数据库之前必先获得驱动类。

5.2 Statement 接口 

常用的子接口PreparedStatement接口。

PreparedStatement是操作对象。负责通过Connection的连接,向数据库执行SQL语句。

5.3 ResultSet接口

ResultSet是结果集接口,当我们使用PreparedStatement向数据库执行Select命令时,Java程序需要能接收查询结果

在Java程序端就是使用ResultSet对象来接收查询结果的。

数据库的查询结果就是一个二维表格。

ResultSet也是一个表格的处理方式。

六、使用JDBC操作数据库的基本步骤

使用JDBC操作数据库的步骤就三步

        1 打开连接

                将Java程序与数据库创建连接。

        2 操作数据库

                通过Java程序向数据库发送SQL语句执行。

                分增删改,和 查询。

                查询有返回值,返回值叫结果集。

        3 关闭连接

                释放资源,一定要执行的部分。finally块。

6.1 打开连接

打开连接,先获取连接数据库的驱动类

        驱动类是数据库厂商提供,是个Jar包。

还要再确定四个参数:

        1 驱动类的名称

        2 连接数据库的字符串

        3 数据库账号

        4 密码 

//四大参数
String driverClassName="oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@localhost:1521:XE";
String userName = "no5";
String userPass = "123456";
//获得连接对象
Connection conn = DriverManager.getConnection(url,userName,userPass);

6.2 操作数据库

(1) 增删改查 

//通过PreparedStatement对象来操作数据库
String sql = "insert into users_no5(user_id,user_name) values( SEQ01.nextval , '都醒着')";
PreparedStatement pstat = conn.prepareStatement(sql);
pstat.executeUpdate();//执行增删改语句的方法

(2) 查询

rs对象就是一个指向二维表格的引用。rs对象每次只能指向一行。通过next()向下移动一行。

通过getXXX()方法获取指定列的值。

//通过PreparedStatement对象来操作数据库
String sql = "select * from users_no5";
PreparedStatement pstat = conn.prepareStatement(sql);
ResultSet rs = pstat.executeQuery();//rs就是一个二维表格。
while(rs.next()){//向下移一行。
    System.out.println(rs.getInt("user_id"));
    System.out.println(rs.getString("user_name"));
    System.out.println("----------");
}

6.3 关闭数据库连接对象

//关闭连接
conn.close();

七、预编译的操作接口PreparedStatement

使用PreparedStatement可以提前将要执行的SQL语句进行数据库端的预编译

使用PreparedStatement可以提高执行效率。并安全。

7.1 在哪里提前写SQL语句 

在创建pstat对象时,就以及提前将SQL写好了。

String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,'XXX')" ;
PreparedStatement pstat = conn.prepareStatement(sql);

7.2 可以使用SQL语句中定义变量

String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,?)" ;
PreparedStatement pstat = conn.prepareStatement(sql);

7.3 可以使用setXXX()方法为?赋值

一定是在SQL语句执行之前,先为所有的?号进行赋值。

public abstract void setString(int parameterIndex,String x)

        参数:parameterIndex 表示?的索引。从1开始。

                  x  表示值。

String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,?)" ;
PreparedStatement pstat = conn.prepareStatement(sql);
pstat.setString(1,"???");
int i = pstat.executeUpdate();

经典面试题:Statement与PreparedStatement之间的区别?

1 PreparedStatement因为是预编译的原因,可以使用占位符?,这样在之后可以调用set方法来灵活改变SQL语句的内容。

2 PreparedStatement的在执行一次是的开销要大于Statement,但是面对一次性操作也不会带来更多的好处。

3 在大量对象的情况下,由于PreparedStatement是预编译,所以在执行时效率更高。

八、结果集对象 - ResultSet接口 

ResultSet对象是用来接收查询结果的对象。

数据库的查询结果就是一个二维表格

rs对象就是一个指向查询结果每一行的一个指针。

8.1 如何获得RS对象

通过pstat对象执行 executeQuery()方法获得一个RS对象。

String sql = "select * from users_no5 order by user_id ";
PreparedStatement pstat = conn.prepareStatement(sql);
ResultSet rs = pstat.executeQuery();

8.2 EOF和BOF

BOF表示第一行的上面。

EOF表示最后一行的下面。

rs对象就是一个指向查询结果每一行的一个指针。

查询结果能不能没有记录?可以。

所以rs对象默认指向BOF。只有调用一次next()方法才能指向第一行。

如果rs对象调用next()方法,指向了EOF,就表示当前查询结果已经没有了。next()方法返回false;

8.3 next()方法 

boolean next() throws SQLException;

        向一移动一行。当指向EOF时返回false。

ResultSet rs = pstat.executeQuery();
while(rs.next()){
    int userId = rs.getInt("user_id");
    String name = rs.getString("user_name");
    System.out.println(userId+":"+name);
    System.out.println("----------");
}

8.4 getString()方法

String getString(String columnLabel) throws SQLException;

int getInt(String columnLabel) throws SQLException;

 九、封装JDBC的操作

9.1 封装原则

封装原则:将变化的    不变化的 分开进行封装。

以实现,在需求改时,对变化的部分进行修改时,不会影响到不变化 的部分。

9.2 数据库操作步骤的封装 

数据库的操作步骤:3步。

1 打开连接

2 操作数据库

3 关闭连接

变化 :2 操作数据库 (DAO 数据访问对象)

这部分内容是根据操作的不同,代码不一致。

针对不同的表。针对同一张表的不同的CRUD。

不变化 :1 打开连接    3 关闭连接  (工具类)

不管对数据库进行什么操作, 都必须先打开连接,最后关闭连接。

十、封装连接对象的工具类(第一版) 

在这个工具类中,要封装二个行为。

        1 打开连接

        3 关闭连接

/**
 * 在这个工具类中,要封装二个行为。
 * 1 打开连接   getConn()
 * 3 关闭连接   closeConn()
 */
public class ConnUtils {
    private static Connection conn;
    private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:XE";//SID数据库实例名
    private static final String USER_NAME = "no5";
    private static final String USER_PASS = "123456";
    private static final String CLASS_NAME = "oracle.jdbc.driver.OracleDriver";

    /**
     * 打开连接
     *
     * @return 连接对象
     */
    public static Connection getConn() throws SQLException {
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(URL, USER_NAME, USER_PASS);
        }
        return conn;
    }

    /**
     * 关闭连接
     */
    public static void closeConn() throws SQLException {
        try {
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        } finally {
            conn = null;
        }
    }
}

十一、编写实体类,映射数据库表

数据库表:users_no5

表示的是用户信息。

表中的每一第记录就是一个用户实体

表结构:

????转换到Java程序来。

用户实体  在Java程序中使用什么表示?

//users_no5
public class Users {
    //    USER_ID  NUMBER(7,0)
    private Integer userId;
    //    USER_NAME    VARCHAR2(20 BYTE)
    private String userName;
}
public static void main(String[] args) {
    Users user = new Users();
    user.setUserId(1);
    user.setUserName("张三");

    System.out.println(user);
}

十二、编写DAO

DAO:数据访问对象。负责封装操作数据库的代码。

12.1 编写UserDAO类

案例JdbcTest03:向Users_no5插入一条记录

案例JdbcTest04:查询Users_no5所有记录

需求在UsersDAO类中编写二个方法,封装以上二个案例的内容。

封装的方法原型:

案例JdbcTest03:public void insert(Users user);

案例JdbcTest04:public List<Users> selectAll();

public class UsersDAO {
    public void update(Users user) throws SQLException {
        String sql = "update users_no5 set user_name = ? where user_id = ?" ;
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        pstat.setString(1,user.getUserName());
        pstat.setInt(2,user.getUserId());
        pstat.executeUpdate();
    }

    public void delete(Integer userId) throws SQLException {
        String sql = "delete from users_no5 where user_id = ?" ;
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        pstat.setInt(1,userId);
        pstat.executeUpdate();
    }

    public void insert(Users user) throws SQLException {
        String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,?)" ;
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        pstat.setString(1,user.getUserName());
        pstat.executeUpdate();
    }

    /**
     * 按主键查询用户对象的方法
     * @param userId 用户编号
     * @return 用户对象,如果没有找到返回null
     * @throws SQLException
     */
    public Users selectById(Integer userId) throws SQLException {
        String sql = "select * from users_no5 where user_id = ?";
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        pstat.setInt(1,userId);
        ResultSet rs = pstat.executeQuery();
        if(rs.next()){
            Users user = new Users();
            user.setUserId(rs.getInt("user_id"));
            user.setUserName(rs.getString("user_name"));
            return user;
        }else{
            return null;
        }
    }
    public List<Users> selectAll() throws SQLException {
        List<Users> usersList = new ArrayList<>();
        String sql = "select * from users_no5 order by user_id";
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        //查询操作一定要接收查询结果
        ResultSet rs = pstat.executeQuery();
        while(rs.next()){
            Users user = new Users();
            user.setUserId(rs.getInt("user_id"));
            user.setUserName(rs.getString("user_name"));
            usersList.add(user);
        }
        return usersList;
    }
}
public class UsersDAOTest {
    public static void main(String[] args) throws SQLException {
        Users user = new Users();
        user.setUserName("张光明");

        UsersDAO usersDAO = new UsersDAO();

        usersDAO.insert(user);

        List<Users> usersList = usersDAO.selectAll();

        System.out.println(usersList);

        System.out.println("end!");
    }
}

十三、 使用JUnit进行单元测试

Junit是一个单元测试的软件。

我们编写的类中方法,使用Junit可以方便的进行单元测试。

public class UsersDAOTest {
    @Test
    public void selectAll() throws SQLException {
        UsersDAO usersDAO = new UsersDAO();
        List<Users> usersList = usersDAO.selectAll();
        System.out.println(usersList);
    }

    @Test
    public void insert() throws SQLException {
        Users user = new Users();
        user.setUserName("胡伟");
        UsersDAO usersDAO = new UsersDAO();
        usersDAO.insert(user);
    }

    @Test
    public void update() {
        try {
            UsersDAO usersDAO = new UsersDAO();
            Users user = new Users();
            user.setUserId(4);
            user.setUserName("巩鹏程");
            usersDAO.update(user);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

十四、封装连接对象的工具类(第二版) 

14.1 第一版

public class ConnUtils {
    private static Connection conn;
    private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:XE";  
    private static final String USER_NAME = "no5";
    private static final String USER_PASS = "123456";
    private static final String CLASS_NAME = "oracle.jdbc.driver.OracleDriver";

    public static Connection getConn() throws SQLException {
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(URL, USER_NAME, USER_PASS);
        }
        return conn;
    }

    public static void closeConn() throws SQLException {
        try {
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        } finally {
            conn = null;
        }
    }
}

第一版在单用户使用时,没有问题。

我们后面一定会学习web应用程序开发的。像taobao,jd这些是web应用程序?

Web应用程序本身就是一个多用户的应用程序。每一个用户是一个单独的线程。

private static Connection conn;

static 静态,属于类的属性,整个应用程序只有一份。

单用户应用时,只用一个连接对象没关系。

多用户应用时,就变成所有用户使用同一个连接对象

要求:操作数据库一共有3个步骤。第3步是什么?关闭连接

14.2 ThreadLocal类

本地线程容器对象。每一个线程都有一个自己的ThreadLocal对象。

这个对象只能保存一个内容。

14.3 使用ThreadLocal 类修改ConnUtils工具类

public class ConnUtils {
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
    private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
    private static final String USER_NAME = "no5";
    private static final String USER_PASS = "123456";
    private static final String CLASS_NAME = "oracle.jdbc.driver.OracleDriver";
    /**
     * 打开连接
     *
     * @return 连接对象
     */
    public static Connection getConn() throws SQLException {
        Connection conn = THREAD_LOCAL.get();
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(URL, USER_NAME, USER_PASS);
            THREAD_LOCAL.set(conn);
        }
        return conn;
    }
    /**
     * 关闭连接
     */
    public static void closeConn() throws SQLException {
        try {
            Connection conn = THREAD_LOCAL.get();
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        } finally {
            THREAD_LOCAL.set(null);
        }
    }
}

十五、练习:针对Goods_no5

15.1 Goods_no5 数据表

15.2 编写实体类

//Goods_no5
public class Goods {
    //    GOODS_ID NUMBER(7,0)
    private Integer goodsId;
    //    GOODS_NAME   VARCHAR2(200 BYTE)
    private String goodsName;
    //    GOODS_PRICE  NUMBER(7,2)
    private Double goodsPrice;
    //    GOODS_DESC   VARCHAR2(2000 BYTE)
    private String goodsDesc;
    //    GOODS_TYPE_ID    NUMBER(7,0)
    private Integer goodsTypeId;

15.3 编写GoodsDAO类

(1) 按商品名称模糊查询

public List<Goods> selectByName(String goodsName) throws SQLException {
    List<Goods> goodsList = new ArrayList<>();
    String sql = "select * from goods_no5 where goods_name like ?";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setString(1,"%"+goodsName+"%");
    ResultSet rs = pstat.executeQuery();
    while(rs.next()){
        Goods goods = new Goods();
        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));
        goods.setGoodsTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsList.add(goods);
    }
    return goodsList;
}

测试类:

public class GoodsDAOTest {
    private GoodsDAO goodsDAO = new GoodsDAO();
    @Test
    public void selectByName() throws SQLException {
        System.out.println(goodsDAO.selectByName("小米"));
    }
}

(2) 按商品价格区间查询

public List<Goods> selectByPrice(Double min,Double max);

public List<Goods> selectByPrice(Double min,Double max) throws SQLException {
    List<Goods> goodsList = new ArrayList<>();
    String sql = "select * from goods_no5 where goods_price between ? and ?";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setDouble(1,min);
    pstat.setDouble(2,max);
    ResultSet rs = pstat.executeQuery();
    while(rs.next()){
        Goods goods = new Goods();
        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));
        goods.setGoodsTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsList.add(goods);
    }
    return goodsList;
}

十六、多表查询

16.1 表与表之间的关系

在数据库中有一对一,一对多,多对一,多对多。

        针对每一张表,就只有两个映射基数。(一 和 多)

Java中,一对多多对一不是一个事。

        表被映射为一个类。

        类与类之间如何表示数据库中的映射基数,

                一 : 对象

                多 : 集合

16.2 编写映射Type_no5表的实体类

本次我们九先不去讨论 自连接 

实体类:

//type_no5表
public class Types {
    //    TYPE_ID  NUMBER(7,0)
    private Integer typeId;
    //    TYPE_NAME    VARCHAR2(20 BYTE)
    private String typeName;
    //    TYPE_PID NUMBER(7,0)
    private Integer typePid;
    //    TYPE_LEVEL   NUMBER(7,0)
    private Integer typeLevel;
    //    TYPE_PATH    VARCHAR2(2000 BYTE)
    private String typePath;

16.3 在实体类之间去表示数据表之间的关系

         在数据库中整理一下:就二种: X对一X对多

        X对一 在实体类中就是一个  对象。

        X对多  在实体类中就是一个   集合。

使用实体类表示 商品 与 类型 的关系  完整结构如下:

(1) 商品属于哪一个类型? 对一

//Goods_no5
public class Goods {
    //    GOODS_ID NUMBER(7,0)
    private Integer goodsId;
    //    GOODS_NAME   VARCHAR2(200 BYTE)
    private String goodsName;
    //    GOODS_PRICE  NUMBER(7,2)
    private Double goodsPrice;
    //    GOODS_DESC   VARCHAR2(2000 BYTE)
    private String goodsDesc;
    //    GOODS_TYPE_ID    NUMBER(7,0)
    private Types goodsType;//商品属于哪一个类型。多对一

(2) 类型有哪些商品?一对多

//type_no5表
public class Types {
    //    TYPE_ID  NUMBER(7,0)
    private Integer typeId;
    //    TYPE_NAME    VARCHAR2(20 BYTE)
    private String typeName;
    //    TYPE_PID NUMBER(7,0)
    private Integer typePid;
    //    TYPE_LEVEL   NUMBER(7,0)
    private Integer typeLevel;
    //    TYPE_PATH    VARCHAR2(2000 BYTE)
    private String typePath;
    // 类型有哪些商品?    一对多。
    private List<Goods> goodsList;

16.4 编写GoodsDAO

必须使用连接查询,才能在结果集中获取到商品表与类型表 , 二张表的字段。

select * from goods_no5 left join type_no5 on goods_type_id = type_id

public List<Goods> selectByName(String goodsName) throws SQLException {
    List<Goods> goodsList = new ArrayList<>();
    String sql = "select * from goods_no5 " +
            " left join type_no5 on goods_type_id = type_id " +
            " where goods_name like ? ";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setString(1,"%"+goodsName+"%");
    ResultSet rs = pstat.executeQuery();
    while(rs.next()){
        Goods goods = new Goods();
        Types goodsType = new Types();

        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));

        goodsType.setTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsType.setTypeName(rs.getString("TYPE_NAME"));
        goodsType.setTypeLevel(rs.getInt("TYPE_LEVEL"));
        goodsType.setTypePath(rs.getString("TYPE_PATH"));
        goodsType.setTypePid(rs.getInt("TYPE_PID"));

        goods.setGoodsType(goodsType);//维护关系
        goodsList.add(goods);
    }
    return goodsList;
}

保存一个商品。

/**
 * 向商品表中插入记录的方法
 * @param goods 商品对象
 * @throws SQLException
 */
public void insert(Goods goods) throws SQLException {
    String sql = "insert into " +
            " goods_no5(GOODS_ID,GOODS_NAME,GOODS_PRICE,GOODS_DESC,GOODS_TYPE_ID) " +
            " values(seq01.nextval,?,?,?,?)";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setString(1,goods.getGoodsName());
    pstat.setDouble(2,goods.getGoodsPrice());
    pstat.setString(3,goods.getGoodsDesc());
    pstat.setInt(4,goods.getGoodsType().getTypeId());
    pstat.executeUpdate();
}

测试类: 

public class GoodsDAOTest {
    private GoodsDAO goodsDAO = new GoodsDAO();

    @Test
    public void insert() throws SQLException {
        Types goodsType = new Types();
        goodsType.setTypeId(2);//在数据库中维护关系只靠主键。

        Goods goods = new Goods();
        goods.setGoodsDesc("小心别买错!");
        goods.setGoodsName("雷碧");
        goods.setGoodsPrice(3.0);
        goods.setGoodsType(goodsType);

        goodsDAO.insert(goods);
    }

十七、分层结构的业务层 - Service

17.1 业务层赋值处理业务逻辑

例如:增加商品时,商品名不可以重复。

例如:修改数量时,数量不能小于0。

17.2 编写Service类时,要处理哪些内容? 

在我们编写的Service类中,要处理如下三件事:

        1 异常处理

        2 事务处理

        3 资源释放(连接关闭)

十八、事务是什么 

事务是数据库技术中的一个特性。

事务表示是一组不可分割的整体操作。

事务正常操作就全体执行。否则就全体不执行。

例如:转账是一个事务。

完整的转账操作有二步:

1 A+1000

2 B-1000

转账的二个操作就应该是一个事务整体。

在数据库中操作事务三个动作:

1 打开事务

2 以提交方式关闭事务 确定事务过程中的修改

3 以回滚方式关闭事务 撤消事务过程中的修改

18.1 在JDBC中操作事务 

第一点:同一个事务必须使用相同的Connection对象

第二点:在JDBC中事务操作共三个方法

        1 打开事务 conn.setAutoCommit(false);//关闭自动提交事务

        2 提交事务 conn.commit();

        3 回滚事务 conn.rollback();

conn.setAutoCommit(false); 在JDBC中每一次数据库操作都默认是一个事务,会进行自动事务提交。

第三点:在JDBC中查询默认不需要事务。增删改必须写事务。

十九、编写GoodsService类

19.1 创建GoodsService类

19.2 创建依赖的GoodsDAO对象

public class GoodsService {
    private GoodsDAO goodsDAO = new GoodsDAO();
    
}

19.3 编写查询所有商品的业务方法

public class GoodsService {
    private GoodsDAO goodsDAO = new GoodsDAO();

    public List<Goods> searchAll() {
        try {
            return this.goodsDAO.selectAll();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

19.4 编写删除商品的业务方法

删除方法,以后在实际开发中没事别用

public class GoodsService {
    private GoodsDAO goodsDAO = new GoodsDAO();

    public void deleteById(Integer goodsId){
        try {
            ConnUtils.getConn().setAutoCommit(false);
            this.goodsDAO.deleteById(goodsId);
            ConnUtils.getConn().commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                ConnUtils.getConn().rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            throw  new RuntimeException(e);
        } finally {
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

19.5 编写增加商品的业务方法

增加商品有业务规则:相同的商品名称不能增加。

如何判断一个商品名称是不是重复?商品名称在数据库中存在不存在?

使用商品名称去查询数据库。

(1) 编写GoodsDAO中按商品名称查询商品对象

public Goods selectByGoodsName(String goodsName);

/**
 * 按商品名称查询商品对象的方法
 * @param goodsName 商品名称
 * @return 商品对象,如果没有找到返回null
 * @throws SQLException
 */
public Goods selectByGoodsName(String goodsName) throws SQLException {
    String sql = "select * from goods_no5 " +
            " left join type_no5 on goods_type_id = type_id " +
            " where goods_name = ? ";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setString(1,goodsName );
    ResultSet rs = pstat.executeQuery();
    if(rs.next()){
        Goods goods = new Goods();
        Types goodsType = new Types();
        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));
        goods.setGoodsType(goodsType);//维护关系
        goodsType.setTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsType.setTypeName(rs.getString("TYPE_NAME"));
        goodsType.setTypeLevel(rs.getInt("TYPE_LEVEL"));
        goodsType.setTypePath(rs.getString("TYPE_PATH"));
        goodsType.setTypePid(rs.getInt("TYPE_PID"));
        return goods;
    }else{
        return null;
    }
}

(2) 编写自定义异常类NameException

业务有例外:当商品名称重复时不能增加。

public class NameException extends Exception{
    public NameException(String message) {
        super(message);
    }
}

(3) 编写GoodsService类中增加方法

public void save(Goods goods) throws NameException;

public class GoodsService {
    private GoodsDAO goodsDAO = new GoodsDAO();

    /**
     * 保存新商品的方法
     * @param goods 新商品对象
     * @throws NameException 当商品名称已经存在时,抛出异常。
     */
    public void save(Goods goods) throws NameException {
        try {
            if (this.goodsDAO.selectByGoodsName(goods.getGoodsName()) == null) {
                ConnUtils.getConn().setAutoCommit(false);
                this.goodsDAO.insert(goods);
                ConnUtils.getConn().commit();
            } else {
                throw new NameException("商品名称已经存在!");
            }
        } catch (NameException e) {
            e.printStackTrace();
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                ConnUtils.getConn().rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            throw new RuntimeException(e);
        } finally {
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

(4) 编写测试类

public class GoodsServiceTest {
    private GoodsService goodsService = new GoodsService();
    @Test
    public void save(){
        Goods goods = new Goods();
        goods.setGoodsPrice(5000.0);
        goods.setGoodsName("入门机单反");
        goods.setGoodsDesc("除了贵点没什么优点!");
        Types goodsType = new Types();
        goodsType.setTypeId(4);
        goods.setGoodsType(goodsType);

        try {
            goodsService.save(goods);
            System.out.println("商品保存成功!");
        } catch (NameException e) {
            e.printStackTrace();
            System.out.println(e.getMessage()+"保存失败!请重新输入!");
        }
    }
}

二十、整理数据表关系的最佳实践

20.1 数据库表的关系

基本上在开发时,我们大部分时间处理的关系都是多对一

在数据库中维护关系只需要依靠 外键 字段。

商品表(goods_no5)与 类型表(type_no5)之间的关系依赖 goods_type_id

20.2 在Java中的关系可以双向映射

两类可以分别描述关系的

//Goods_no5
public class Goods {
    //    GOODS_ID NUMBER(7,0)
    private Integer goodsId;
    //    GOODS_NAME   VARCHAR2(200 BYTE)
    private String goodsName;
    //    GOODS_PRICE  NUMBER(7,2)
    private Double goodsPrice;
    //    GOODS_DESC   VARCHAR2(2000 BYTE)
    private String goodsDesc;
    //    GOODS_TYPE_ID    NUMBER(7,0)
    private Types goodsType;//商品属于哪一个类型。多对一

 //type_no5表
public class Types {
    //    TYPE_ID  NUMBER(7,0)
    private Integer typeId;
    //    TYPE_NAME    VARCHAR2(20 BYTE)
    private String typeName;
    //    TYPE_PID NUMBER(7,0)
    private Integer typePid;
    //    TYPE_LEVEL   NUMBER(7,0)
    private Integer typeLevel;
    //    TYPE_PATH    VARCHAR2(2000 BYTE)
    private String typePath;
    // 一个类型下的多个商品,一对多关系。
    private List<Goods> goodsList;

20.3 最佳实践 

(1) 当多对一时,可以直接使用多表连接查询。

        因为多对一时,本身的每一条记录,只能连接出一条记录。

        在记录数量上不会发生改变的。

String sql = "select * from goods_no5 " +
        " left join type_no5 on goods_type_id = type_id ";

2 当一对多时,目前我们不能使用多表连接查询。

        因为一对多时,本身的每一条记录,有可能连接出多条记录。

        在记录数量上会发生改变的。

        所以我们需要大家编写二个DAO的方法。

select * from type_no5 where type_id = 2 ;
select * from goods_no5 where goods_type_id = 2;

        在service层完成关系的维护

20.4 编写TypeDAO类

按类型编号查询类型对象的方法

public Types selectById(Integer typeId);

public class TypeDAO {
    public Types selectById(Integer typeId) throws SQLException {
        String sql = "select * from type_no5 where type_id = ? ";
        Connection conn = ConnUtils.getConn();
        PreparedStatement pstat = conn.prepareStatement(sql);
        pstat.setInt(1,typeId);
        ResultSet rs = pstat.executeQuery();
        if(rs.next()){
            Types type = new Types();
            type.setTypeId(rs.getInt("TYPE_ID"));
            type.setTypeName(rs.getString("TYPE_NAME"));
            type.setTypePid(rs.getInt("TYPE_PID"));
            type.setTypeLevel(rs.getInt("TYPE_LEVEL"));
            type.setTypePath(rs.getString("TYPE_PATH"));
            return type;
        }else{
            return null;
        }
    }
}

20.5 编写GoodsDAO类

按类型编号查询商品集合的方法

public List<Goods> selectByType(Integer typeId);

public List<Goods> selectByType(Integer typeId) throws SQLException {
    List<Goods> goodsList = new ArrayList<>();
    String sql = "select * from goods_no5 " +
            " left join type_no5 on goods_type_id = type_id " +
            " where goods_type_id = ?";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    pstat.setInt(1,typeId);
    ResultSet rs = pstat.executeQuery();
    while(rs.next()){
        Goods goods = new Goods();
        Types goodsType = new Types();

        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));
        goods.setGoodsType(goodsType);//维护关系

        goodsType.setTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsType.setTypeName(rs.getString("TYPE_NAME"));
        goodsType.setTypeLevel(rs.getInt("TYPE_LEVEL"));
        goodsType.setTypePath(rs.getString("TYPE_PATH"));
        goodsType.setTypePid(rs.getInt("TYPE_PID"));

        goodsList.add(goods);
    }
    return goodsList;
}

20.6 编写TypeService类

按类型编号查询类型对象的方法

public Types searchById(Integer typeId);

在这个方法中,我们需要调用TypeDAO. selectById()方法,和 GoodsDAO. selectByType()方法。

完成关系的维护。

 

public class TypeService {
    private TypeDAO typeDAO = new TypeDAO();
    private GoodsDAO goodsDAO = new GoodsDAO();

    /**
     * 按类型编号查询类型对象的方法
     * @param typeId 类型编号
     * @return 类型对象,并绑定了类型下的商品集合。如果没有找到返回null
     */
    public Types searchById(Integer typeId){
        try {
            Types type = this.typeDAO.selectById(typeId);
            if(type!=null){
                List<Goods> goodsList = this.goodsDAO.selectByType(typeId);
                type.setGoodsList(goodsList);//在service中完成关系的维护
            }
            return type;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally{
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类:

public class TypeServiceTest {
    private TypeService typeService = new TypeService();
    @Test
    public void searchById(){
        int typeId = 2;
        Types type = this.typeService.searchById(typeId);
        System.out.println(type);
        System.out.println(type.getGoodsList());
    }
}

二十一、封装连接对象的工具类(第三最终版)

21.1 第二版的问题

第二版中,想要获得一个连接对象,就是需要新创建一个。当一个业务方法完成之后,我们要关闭连接对象。

但是,在整个操作数据库的过程,创建连接和关闭连接是最浪费资源的操作。

解决思路:如果能不用每次都关闭连接。大家能共用同一个连接。可以很好的提高效率。

21.2 连接池技术

池:容器。

连接池:一个保存大量连接的容器。

连接池的作用是什么?

        当我们需要去操作数据库时,可以从连接池中获得一个连接对象,进行数据库操作,操作完成之后,释放连接对象。

以这种方式使用连接池技术,可以有效的减少创建连接对象的次数。达到提交效率。

市面上主流的连接池技术有什么?C3P0,DBCP,Durid。

 21.3 数据源对象

java.sql.DataSource对象。称为数据源对象,就是Java程序实现连接池技术的具体对象。

只不过。我们可以使用DBCP等连接池技术来获得数据源对象。

DataSource对象 才是在Java程序中保存大量连接对象的容器。

21.4 使用DBCP创建DataSource

第一步:导入DBCP的jar文件

 第二步:通过一个properties文件配置DBCP连接池

        =号左边的关键字,是DBCP连接池使用的。所以一个名字不允许你修改。

driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:XE
username=no5
password=123456
maxActive=50
maxIdle=20
maxWait=60000

maxActive 最大活动数量,表示连接池中最多有多个连接对象。

maxIdle 最大空闲数量,表示连接池中最多保留几个闲置连接对象。

maxWait 最大等待时长,单位毫秒。

第三步:使用DBCP连接池技术创建DataSource对象 

public class DBCPTest {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(DBCPTest.class.getResourceAsStream("/oracle.properties"));
        //获得数据源对象
        DataSource ds = BasicDataSourceFactory.createDataSource(properties);
        System.out.println(ds);
        //从数据源中获得一个Connection对象。
        Connection conn = ds.getConnection();
        System.out.println(conn.getClass().getName());
        conn.close();//释放资源
    }
}

21.5 使用DBCP去封装连接对象的工具类

public class ConnUtils {
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
    private static DataSource ds = null;

    static{
        Properties properties = new Properties();
        try {
            properties.load(ConnUtils.class.getResourceAsStream("/oracle.properties"));
            //获得数据源对象
            ds = BasicDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 打开连接
     *
     * @return 连接对象
     */
    public static Connection getConn() throws SQLException {
        Connection conn = THREAD_LOCAL.get();
        if (conn == null || conn.isClosed()) {
            conn = ds.getConnection();
            THREAD_LOCAL.set(conn);
        }
        return conn;
    }
    /**
     * 关闭连接
     */
    public static void closeConn() throws SQLException {
        try {
            Connection conn = THREAD_LOCAL.get();
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        } finally {
            THREAD_LOCAL.set(null);
        }
    }
}

二十二、分页查询

22.1 分页查询

分页查询是现在开发的基础功能。

进行数据展示都需要进行分页查询。

22.2 Oracle数据库的分页语句

Oracle中进行分页查询,需要使用到ROWNUM的伪列。

ROWNUM : Oracle数据库中为每一行查询结果编写一个编号的伪列。

 22.3 ROWNUM的使用

RowNum是先为查询到的第一行,加数字1编号,之后再进行where条件的判断。

如果where满足条件就把编号1确定下来。为第二行使用编号2。

如果第一行where不满足条件,就为第二行再次使用编号1。

select rownum rn , goods_no5.*   from goods_no5

where rownum >=2 and  rownum <= 5;

22.4 如何使用rownum

使用子查询,先确定查询的结果,再为结果增加编号。

使用ROWNUMOracle中进行分页查询时,一定是三层子查询嵌套。

select * from (select rownum rn ,  t.* from (
  select * from goods_no5 order by goods_no5.goods_price desc
) t ) temp where temp.rn >=1 and temp.rn <=2 

格式:

select * from (select rownum rn ,  t.* from (

  查询的SQL语句

) t ) temp where temp.rn >= A and temp.rn <= B

A= ((页码-1)*每页记录数量)+1

B= 页码*每页记录数量

22.5 分页数据

当进行分页查询时,必须使用到的数据有哪些?

1 page 页码:表示你要查看第几页 是由用户指定

2 recordOfPage 每页记录数量:表示每页查询到的记录条数 由程序员指定

3 pageCount 总页码:将所有数据进行分页时,一共分成了多少页 计算获得

pageCount = ((recordCount-1) / recordOfPage ) + 1

4 recordCount 总记录数量:当前查询语句应该查询到的所有记录的数量 查询获得

select count(*) from 表 where 条件

5 List<T> 当页记录数据:分页时查询到的内容 分页查询获得

select * from (select rownum rn ,  t.* from (

   Select * from 表 where 条件

) t ) temp where temp.rn >= ((page-1)*recordOfPage)+1 and temp.rn <= (page*recordOfPage)

 

22.6 封装分页数据的值对象

public class PageVO<T> {
    //1 page         页码:表示你要查看第几页      是由用户指定
    private Integer page;
    //2 recordOfPage   每页记录数量:表示每页查询到的记录条数    由程序员指定
    private Integer recordOfPage;
    //3 pageCount     总页码:将所有数据进行分页时,一共分成了多少页    计算获得
    private Integer pageCount;
    //4 recordCount    总记录数量:当前查询语句应该查询到的所有记录的数量  查询获得
    private Integer recordCount;
    //5 List<T>       当页记录数据:分页时查询到的内容。  分页查询获得
    private List<T> list;

PageVO<Goods> pageVO = new PageVO<>();

PageVO<Types> pageVO = new PageVO<>();

22.7 封装分页查询语句的工具类

public class SqlUtils {
    /**
     * 动态获取Oracle分页查询语句
     * @param sql 查询语句
     * @param page 当前页码
     * @param recordOfPage 每页记录数量
     * @return 封装好的分页查询语句
     */
    public static String getPageSql(String sql,Integer page,Integer recordOfPage){
        String pageSql = "select * from (select rownum rn ,  t.* from ( " ;
        pageSql += sql ;
        pageSql += " ) t ) temp where temp.rn >= "
                + (((page-1)*recordOfPage)+1)
                +" and temp.rn <= "+(page*recordOfPage) ;
        return pageSql;
    }

22.8 编写GoodsDAO类

public Integer getRecordCount();//获取总记录数量

/**
 * 查询记录总数量的方法
 * @return 记录总数量
 * @throws SQLException
 */
public Integer getRecordCount() throws SQLException {
    String sql = "select count(*) from goods_no5";
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(sql);
    ResultSet rs = pstat.executeQuery();
    rs.next();
    return rs.getInt(1);
}

public List<Goods> search(Integer page , Integer recordOfPage);

/**
 * 分页查询方法
 * @param page 当前页码
 * @param recordOfPage 每页记录数量
 * @return 当前查询的数据
 */
public List<Goods> search(Integer page , Integer recordOfPage) throws SQLException {
    String sql = "select * from goods_no5 order by goods_price desc";
    String pageSql = SqlUtils.getPageSql(sql,page,recordOfPage);
    List<Goods> goodsList = new ArrayList<>();
    Connection conn = ConnUtils.getConn();
    PreparedStatement pstat = conn.prepareStatement(pageSql);
    ResultSet rs = pstat.executeQuery();
    while(rs.next()){
        Goods goods = new Goods();
        Types goodsType = new Types();

        goods.setGoodsId(rs.getInt("GOODS_ID"));
        goods.setGoodsName(rs.getString("GOODS_NAME"));
        goods.setGoodsPrice(rs.getDouble("GOODS_PRICE"));
        goods.setGoodsDesc(rs.getString("GOODS_DESC"));
        goods.setGoodsType(goodsType);//维护关系

        goodsType.setTypeId(rs.getInt("GOODS_TYPE_ID"));
        goodsType.setTypeName(rs.getString("TYPE_NAME"));
        goodsType.setTypeLevel(rs.getInt("TYPE_LEVEL"));
        goodsType.setTypePath(rs.getString("TYPE_PATH"));
        goodsType.setTypePid(rs.getInt("TYPE_PID"));

        goodsList.add(goods);
    }
    return goodsList;
}

测试类:

public class GoodsDAOTest {
    private GoodsDAO goodsDAO = new GoodsDAO();

    @Test
    public void search() throws SQLException {
        List<Goods> goodsList = this.goodsDAO.search(2,2);
        System.out.println(goodsList);
    }

    @Test
    public void getRecordCount() throws SQLException {
        System.out.println(this.goodsDAO.getRecordCount());
    }
}

22.9 编写GoodsService类

在业务类中分页,需要提交1个数据。页码。

public PageVO<Goods> search(Integer page);

public class GoodsService {
    private GoodsDAO goodsDAO = new GoodsDAO();

    /**
     * 分页查询的业务方法
     * @param page 当前页码
     * @return 分页值对象
     */
    public PageVO<Goods> search(Integer page){
        try {
            PageVO<Goods> goodsPageVO = new PageVO<>();
            //定义每页记录数量
            Integer recordOfPage = 3;
            //总记录数量
            Integer recordCount = this.goodsDAO.getRecordCount();
            //总页数
            Integer pageCount = ((recordCount-1)/recordOfPage) + 1;
            //当前页码
            if(page==null) page = 1;
            if(page < 1) page = 1;
            if(page > pageCount) page = pageCount;
            //当前数据
            List<Goods> goodsList = this.goodsDAO.search(page,recordOfPage);

            goodsPageVO.setPage(page);
            goodsPageVO.setPageCount(pageCount);
            goodsPageVO.setRecordOfPage(recordOfPage);
            goodsPageVO.setRecordCount(recordCount);
            goodsPageVO.setList(goodsList);

            return goodsPageVO;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally{
            try {
                ConnUtils.closeConn();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

测试类:

public class GoodsServiceTest {
    private GoodsService goodsService = new GoodsService();
    @Test
    public void search(){
        int page = -2;
        System.out.println(this.goodsService.search(page));
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修贤323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值