目前最好的关系型数据库。
基本的CRUD命令
SQL语句。select(R),update(U),delete(D),insert(C)
中小型项目非常好用的关系型数据库。
灵活,小巧。
ER图---》根据需求分析数据库结构。
三范式---》学会使用范式来规范表结构
在Java中连接数据库技术。
在Java代码中想操作数据库中的数据,就必须使用到JDBC技术。
提供JDBC从Java代码中想数据库发送执行的SQL语句。
工具类的封装。DAO-Service 的分层。
一定要遵守编码规范。
就是一套使用的规范。大家按规范编写就没有什么特别难理解的部分。
难点:
a 要使用连接池技术
b ThreadLocal类。理解上需要大家想。
a 按规范比那些代码。
b 练习
JDBC : Java DataBase Connector 中文:Java数据库连接技术
JDBC 是一种结束。一种在Java程序中连接数据库的技术。
使用JDBC可以实现在Java代码中去操作数据库表中的记录。
JDBC是一个持久化技术
化:过程。
数据是分为零食状态和持久状态。
临时状态:数据保存在内存中。代码在运行时的数据。
持久状态:数据保存在硬盘中,以文件的方式长期保存的数据。
持久化:数据在临时状态 与 持久状态 之间进行切换的过程。
1 将Java程序中的数据保存到数据库中。
2 将数据库职工的数据读取到Java程序中
JDBC是只一套规范,约定了Java语言连接到不同数据库产品时的标准。
例如:USB接口。
任何数据库产品,想让自己的产品,可以使用JDBC连接到Java程序,就要按JDBC的标准编写数据库的驱动。
数据库的驱动是数据库厂商按JDBC标准,由各数据库厂商自己编写的。
在Java程序中接口表示了一种能力,一种规范,一个标准,一种约定。
JDBC本身就是一组接口。各大数据库厂商需要根据自己的数据库去实现这一组接口。
Connection是连接对象,负责创建Java程序与数据库之间的连接。
Connection接口的实现类由各数据库厂商提供。称为驱动类。操作数据库之前必先获得驱动类。
常用的子接口PreparedStatement接口。
PreparedStatement是操作对象。负责通过Connection的连接,向数据库执行SQL语句。
ResultSet是结果集接口,当我们使用PreparedStatement向数据库执行Select命令时,Java程序需要能接收查询结果。
在Java程序端就是使用ResultSet对象来接收查询结果的。
数据库的查询结果就是一个二维表格。
ResultSet也是一个表格的处理方式。
使用JDBC操作数据库的步骤就三步:
1 打开连接
将Java程序与数据库创建连接。
2 操作数据库
通过Java程序向数据库发送SQL语句执行。
分增删改,和 查询。
查询有返回值,返回值叫结果集。
3 关闭连接
释放资源,一定要执行的部分。finally块。
打开连接,先获取连接数据库的驱动类。
驱动类是数据库厂商提供,是个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);
//通过PreparedStatement对象来操作数据库
String sql = "insert into users_no5(user_id,user_name) values( SEQ01.nextval , '都醒着')";
PreparedStatement pstat = conn.prepareStatement(sql);
pstat.executeUpdate();//执行增删改语句的方法
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("----------");
}
//关闭连接
conn.close();
使用PreparedStatement可以提前将要执行的SQL语句进行数据库端的预编译。
使用PreparedStatement可以提高执行效率。并安全。
在创建pstat对象时,就以及提前将SQL写好了。
String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,'XXX')" ;
PreparedStatement pstat = conn.prepareStatement(sql);
String sql = "insert into users_no5(user_id,user_name) values(seq01.nextval,?)" ;
PreparedStatement pstat = conn.prepareStatement(sql);
一定是在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();
1 PreparedStatement因为是预编译的原因,可以使用占位符?,这样在之后可以调用set方法来灵活改变SQL语句的内容。
2 PreparedStatement的在执行一次是的开销要大于Statement,但是面对一次性操作也不会带来更多的好处。
3 在大量对象的情况下,由于PreparedStatement是预编译,所以在执行时效率更高。
ResultSet对象是用来接收查询结果的对象。
数据库的查询结果就是一个二维表格。
rs对象就是一个指向查询结果每一行的一个指针。
通过pstat对象执行 executeQuery()方法获得一个RS对象。
String sql = "select * from users_no5 order by user_id ";
PreparedStatement pstat = conn.prepareStatement(sql);
ResultSet rs = pstat.executeQuery();
BOF表示第一行的上面。
EOF表示最后一行的下面。
rs对象就是一个指向查询结果每一行的一个指针。
查询结果能不能没有记录?可以。
所以rs对象默认指向BOF。只有调用一次next()方法才能指向第一行。
如果rs对象调用next()方法,指向了EOF,就表示当前查询结果已经没有了。next()方法返回false;
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("----------");
}
String getString(String columnLabel) throws SQLException;
int getInt(String columnLabel) throws SQLException;
封装原则:将变化的 和 不变化的 分开进行封装。
以实现,在需求改时,对变化的部分进行修改时,不会影响到不变化 的部分。
数据库的操作步骤: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:数据访问对象。负责封装操作数据库的代码。
案例JdbcTest03:向Users_no5插入一条记录
案例JdbcTest04:查询Users_no5所有记录
需求在UsersDAO类中编写二个方法,封装以上二个案例的内容。
封装的方法原型:
案例JdbcTest03:public void insert(Users user);
案例JdbcTest04:public List
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 selectAll() throws SQLException {
List 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 usersList = usersDAO.selectAll();
System.out.println(usersList);
System.out.println("end!");
}
}
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();
}
}
}
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步是什么?关闭连接。
本地线程容器对象。每一个线程都有一个自己的ThreadLocal对象。
这个对象只能保存一个内容。
public class ConnUtils {
private static final ThreadLocal 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
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;
public List selectByName(String goodsName) throws SQLException {
List 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("小米"));
}
}
public List
public List selectByPrice(Double min,Double max) throws SQLException {
List 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;
}
在数据库中有一对一,一对多,多对一,多对多。
针对每一张表,就只有两个映射基数。(一 和 多)
在Java中,一对多和多对一不是一个事。
表被映射为一个类。
类与类之间如何表示数据库中的映射基数,
一 : 对象
多 : 集合
本次我们九先不去讨论 自连接
实体类:
//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;
在数据库中整理一下:就二种: X对一,X对多。
X对一 : 在实体类中就是一个 对象。
X对多 : 在实体类中就是一个 集合。
使用实体类表示 商品 与 类型 的关系 完整结构如下:
//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 goodsList;
必须使用连接查询,才能在结果集中获取到商品表与类型表 , 二张表的字段。
select * from goods_no5 left join type_no5 on goods_type_id = type_id
public List selectByName(String goodsName) throws SQLException {
List 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);
}
例如:增加商品时,商品名不可以重复。
例如:修改数量时,数量不能小于0。
在我们编写的Service类中,要处理如下三件事:
1 异常处理
2 事务处理
3 资源释放(连接关闭)
事务是数据库技术中的一个特性。
事务表示是一组不可分割的整体操作。
事务正常操作就全体执行。否则就全体不执行。
例如:转账是一个事务。
完整的转账操作有二步:
1 A+1000
2 B-1000
转账的二个操作就应该是一个事务整体。
在数据库中操作事务三个动作:
1 打开事务
2 以提交方式关闭事务 确定事务过程中的修改
3 以回滚方式关闭事务 撤消事务过程中的修改
第一点:同一个事务必须使用相同的Connection对象。
第二点:在JDBC中事务操作共三个方法。
1 打开事务 conn.setAutoCommit(false);//关闭自动提交事务
2 提交事务 conn.commit();
3 回滚事务 conn.rollback();
conn.setAutoCommit(false); 在JDBC中每一次数据库操作都默认是一个事务,会进行自动事务提交。
第三点:在JDBC中查询默认不需要事务。增删改必须写事务。
public class GoodsService {
private GoodsDAO goodsDAO = new GoodsDAO();
}
public class GoodsService {
private GoodsDAO goodsDAO = new GoodsDAO();
public List searchAll() {
try {
return this.goodsDAO.selectAll();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
ConnUtils.closeConn();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
删除方法,以后在实际开发中没事别用。
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();
}
}
}
}
增加商品有业务规则:相同的商品名称不能增加。
如何判断一个商品名称是不是重复?商品名称在数据库中存在不存在?
使用商品名称去查询数据库。
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;
}
}
业务有例外:当商品名称重复时不能增加。
public class NameException extends Exception{
public NameException(String message) {
super(message);
}
}
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();
}
}
}
}
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()+"保存失败!请重新输入!");
}
}
}
基本上在开发时,我们大部分时间处理的关系都是多对一。
在数据库中维护关系只需要依靠 外键 字段。
商品表(goods_no5)与 类型表(type_no5)之间的关系依赖 goods_type_id。
两类可以分别描述关系的
//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;
因为多对一时,本身的每一条记录,只能连接出一条记录。
在记录数量上不会发生改变的。
String sql = "select * from goods_no5 " +
" left join type_no5 on goods_type_id = type_id ";
因为一对多时,本身的每一条记录,有可能连接出多条记录。
在记录数量上会发生改变的。
所以我们需要大家编写二个DAO的方法。
select * from type_no5 where type_id = 2 ;
select * from goods_no5 where goods_type_id = 2;
在service层完成关系的维护
按类型编号查询类型对象的方法
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;
}
}
}
按类型编号查询商品集合的方法
public List
public List selectByType(Integer typeId) throws SQLException {
List 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;
}
按类型编号查询类型对象的方法
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 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());
}
}
第二版中,想要获得一个连接对象,就是需要新创建一个。当一个业务方法完成之后,我们要关闭连接对象。
但是,在整个操作数据库的过程,创建连接和关闭连接是最浪费资源的操作。
解决思路:如果能不用每次都关闭连接。大家能共用同一个连接。可以很好的提高效率。
池:容器。
连接池:一个保存大量连接的容器。
连接池的作用是什么?
当我们需要去操作数据库时,可以从连接池中获得一个连接对象,进行数据库操作,操作完成之后,释放连接对象。
以这种方式使用连接池技术,可以有效的减少创建连接对象的次数。达到提交效率。
市面上主流的连接池技术有什么?C3P0,DBCP,Durid。
java.sql.DataSource对象。称为数据源对象,就是Java程序实现连接池技术的具体对象。
只不过。我们可以使用DBCP等连接池技术来获得数据源对象。
DataSource对象 才是在Java程序中保存大量连接对象的容器。
第一步:导入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=60000maxActive 最大活动数量,表示连接池中最多有多个连接对象。
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();//释放资源
}
}
public class ConnUtils {
private static final ThreadLocal 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);
}
}
}
分页查询是现在开发的基础功能。
进行数据展示都需要进行分页查询。
在Oracle中进行分页查询,需要使用到ROWNUM的伪列。
ROWNUM : Oracle数据库中为每一行查询结果编写一个编号的伪列。
RowNum是先为查询到的第一行,加数字1编号,之后再进行where条件的判断。
如果where满足条件就把编号1确定下来。为第二行使用编号2。
如果第一行where不满足条件,就为第二行再次使用编号1。
select rownum rn , goods_no5.* from goods_no5
where rownum >=2 and rownum <= 5;
使用子查询,先确定查询的结果,再为结果增加编号。
使用ROWNUM在Oracle中进行分页查询时,一定是三层子查询嵌套。
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= 页码*每页记录数量
当进行分页查询时,必须使用到的数据有哪些?
1 page 页码:表示你要查看第几页 是由用户指定
2 recordOfPage 每页记录数量:表示每页查询到的记录条数 由程序员指定
3 pageCount 总页码:将所有数据进行分页时,一共分成了多少页 计算获得
pageCount = ((recordCount-1) / recordOfPage ) + 1
4 recordCount 总记录数量:当前查询语句应该查询到的所有记录的数量 查询获得
select count(*) from 表 where 条件
5 List
当页记录数据:分页时查询到的内容。 分页查询获得 select * from (select rownum rn , t.* from (
Select * from 表 where 条件
) t ) temp where temp.rn >= ((page-1)*recordOfPage)+1 and temp.rn <= (page*recordOfPage)
public class PageVO<T> { |
PageVO |
PageVO |
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;
}
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
/**
* 分页查询方法
* @param page 当前页码
* @param recordOfPage 每页记录数量
* @return 当前查询的数据
*/
public List 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 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 goodsList = this.goodsDAO.search(2,2);
System.out.println(goodsList);
}
@Test
public void getRecordCount() throws SQLException {
System.out.println(this.goodsDAO.getRecordCount());
}
}
在业务类中分页,需要提交1个数据。页码。
public PageVO
public class GoodsService {
private GoodsDAO goodsDAO = new GoodsDAO();
/**
* 分页查询的业务方法
* @param page 当前页码
* @return 分页值对象
*/
public PageVO search(Integer page){
try {
PageVO 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 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));
}
}