Think in Berkeley Database Java Edition 之增删改查实战指南
- 上篇博文其实我们已经对代码做了一定的封装,但是这远远还不够,你有没有发现上篇博文调用的时候还要用myProductDA.pIdx.put(product);这种写法肯定很不舒服,那么本篇我们将再度进行封装,并对增删改查做一定封装。
- 如果看不懂,请先看下之前的博文:1. Think in Berkeley Database Java Edition 概述
- 本篇博文对之前所学做一个总结吧
1.1 BDB Java Edition安装集成
1.1.1 添加项目依赖
如果是Maven项目,那么在pom.xml中添加如下依赖即可:
<!-- https://mvnrepository.com/artifact/berkeleydb/je -->
<dependency>
<groupId>berkeleydb</groupId>
<artifactId>je</artifactId>
<version>3.2.76</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
说明:
- je: 是Oracle Berkeley DB Java Editor 依赖包
- lombok:为了简化增删改查引入的依赖包
- spring-boot-starter-logging:传递依赖使用了SLF4J+Logback日志框架依赖包
1.1.2 数据库环境管理工具类
由于BDB Environment 和EntityStore 打开和关闭与业务无关,通常在程序运行中不需要多个实例,因此我们用懒汉式单例设计模式来封装它
BDBEnvironmentManager.java
package com.xingyun.util.berkeleydb;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.StoreConfig;
import com.sleepycat.persist.model.AnnotationModel;
import com.sleepycat.persist.model.EntityModel;
import com.xingyun.berkeleydb.ProxyForTimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* 功能: Berkeley Database Java Edition 环境管理器
* 作者: 星云
* 时间: 2019/9/6 9:15
*/
@SuppressWarnings("unused")
public final class BDBEnvironmentManager {
//禁用构造方法
private BDBEnvironmentManager() {}
private final static Logger LOGGER = LoggerFactory.getLogger(BDBEnvironmentManager.class);
private static volatile BDBEnvironmentManager bdbEnvironmentManager = null;
// 数据库环境对象
private static Environment myEnvironment = null;
// 数据存储基本单元
private static EntityStore myEntityStore = null;
/**
* @return
*/
public static Environment getMyEnvironment() {
if (myEnvironment != null) {
return myEnvironment;
} else {
return null;
}
}
/**
* @return
*/
public static EntityStore getMyEntityStore() {
if (myEntityStore != null) {
return myEntityStore;
} else {
return null;
}
}
// 懒汉式
/**
* @param envHome
* @param readOnly
* @return
*/
public static BDBEnvironmentManager getInstance(File envHome, Boolean readOnly) {
if (envHome == null) {
return null;
}
if (bdbEnvironmentManager == null) {
// 添加同步锁,会更安全高效
synchronized (BDBEnvironmentManager.class) {
if (bdbEnvironmentManager == null) {
// 代码在这里执行确保应用程序中只有一个实例
bdbEnvironmentManager = new BDBEnvironmentManager();
// 创建一个BDB 环境配置对象
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
// 创建一个数据存储配置对象
StoreConfig myStoreConfig = new StoreConfig();
if (readOnly == null) {
readOnly = false;
}
EntityModel entityModel=new AnnotationModel();
entityModel.registerClass(ProxyForTimeStamp.class);
myStoreConfig.setModel(entityModel);
// 设置该环境是否为只读,true 为只读,false 为可读写
myEnvConfig.setReadOnly(readOnly);
// 设置数据存储配置是否为只读,true 为只读,false 为可读写
myStoreConfig.setReadOnly(readOnly);
// 如果该环境不存在是否重建,true 允许重建,false 不可重建
myEnvConfig.setAllowCreate(!readOnly);
// 如果该存储配置不存在是否重建,true 允许重建,false 不可重建
myStoreConfig.setAllowCreate(!readOnly);
//数据库环境是否支持事务
myEnvConfig.setTransactional(!readOnly);
//存储环境是否支持事务
myStoreConfig.setTransactional(!readOnly);
// 如果文件不存在则创建
if (!envHome.exists()) {
try {
envHome.mkdir();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 打开 environment 和 entity store
if (myEnvironment == null || myEntityStore == null) {
try {
myEnvironment = new Environment(envHome, myEnvConfig);
myEntityStore = new EntityStore(myEnvironment, "EntityStore", myStoreConfig);
} catch (DatabaseException e) {
LOGGER.error("Database Exception",e);
}
}
}
}
}
return bdbEnvironmentManager;
}
// Close the store and environment.
/**
* Close the store and environment
*/
public static void close() {
// 判断存储对象是否为空
if (myEntityStore != null) {
try {
// 尝试关闭存储对象
myEntityStore.close();
} catch (DatabaseException dbe) {
LOGGER.error("Error closing store: " + dbe.toString());
}
}
// 判断环境是否为空
if (myEnvironment != null) {
try {
// 关闭环境
myEnvironment.close();
} catch (DatabaseException dbe) {
LOGGER.error("Error DatabaseException: " + dbe.toString());
}
}
}
}
这样能确保不会打开环境多次,否则会出现这种异常。
The environment cannot be locked for single writer access.
ENV_LOCKED: The je.lck file could not be locked. Environment is
invalid and must be closed.
懒汉模式在使用时,容易引起不同步问题,所以应该创建同步"锁",这样会同步更快,更安全高效,所以代码中对传统的懒汉式代码做了一定的优化,
关于懒汉式还是用恶汉式的选择,如果单件模式实例在系统中经常会被用到,则建议修改此工具类使用恶汉式,如果很少会被用到,那么使用懒汉式比较好,我们的BDB环境一般初始化一次即可,不需要重复获取实例,因此这里使用了懒汉式。
参考博文:Java单例模式--------懒汉式和饿汉式
好了,上述代码已经对JE Environment和EntityStore做了一定的封装。
1.1.3 自定义代理类
一般情况下不需要,但是如果持久化类中有一些特俗字段比如正则表达式或者java.sql.TimeStamp类型,就需要这个操作了。
ProxyForTimeStamp.java
import com.sleepycat.persist.model.Persistent;
import com.sleepycat.persist.model.PersistentProxy;
import java.sql.Timestamp;
/**
* 功能:
* 作者: 星云
* 时间: 2019/9/6 14:20
*/
@Persistent(proxyFor = Timestamp.class)
public class ProxyForTimeStamp implements PersistentProxy<Timestamp> {
Timestamp timestamp;
@Override
public void initializeProxy(Timestamp timestamp) {
this.timestamp=timestamp;
}
@Override
public Timestamp convertProxy() {
return this.timestamp;
}
}
1.1.4 配置类
然后需要有一个配置类来指定BDB数据库的存放位置
import java.io.File;
/**
* 功能:
* 作者: 星云
* 时间: 2019/9/6 9:53
*/
public class BerkeleyDBConfig {
//当前项目根目录
private static final String projectBasePath=new File("").getAbsolutePath();
//src/test/resources
private static final String testResourcePath=projectBasePath+File.separator+"src"+File.separator+"test"+File.separator+"resources";
//src/test/resources/bdb
public static final String BERKELEY_DB_FILE_NAME=testResourcePath+File.separator+"bdb";
}
当然还有很多其他配置方式,这个可以灵活修改
1.1.5 公共接口类封装(非必要,但是建议使用)
由于持久化一般都有一些常用的方法,因此我们抽象出一个公共接口类
BerkeleyDBJpaRepository.java
import java.util.List;
import java.util.Optional;
/**
* 功能: BDB 公用接口封装类
* 作者: 星云
* 时间: 2019/9/6 9:10
*/
public interface BerkeleyDBJpaRepository<T,ID>{
/**
* 保存一个对象到BDB
* @param var1
* @return
*/
<S extends T> S insertObject(S var1);
/**
* 根据Id删除一个对象
* @param var1
* @return
*/
void deleteObjectById(ID var1);
/**
* 修改一个对象
* @param var
* @return
*/
<S extends T> S updateObject(S var);
/**
* 根据Id查找一个对象
* @param var1
* @return
*/
Optional<T> findById(ID var1);
/**
* 查找所有对象
* @return
*/
List<T> findAll();
/**
* 查询总数
* @return
*/
long count();
/**
* 判断对象是否存在
* @param var1
* @return
*/
boolean existsById(ID var1);
}
以上是准备工作,和业务无关,接下来就开始我们使用需要编写的东西了
1.2 编写自己的业务
1.2.1 业务实体类
现在让我们开始写一个业务实体类,假设我们有一个实体类User,拥有成员变量
userId用户Id,userName用户名称,password 用户密码
User.java
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.Persistent;
import com.sleepycat.persist.model.PrimaryKey;
import com.sleepycat.persist.model.Relationship;
import com.sleepycat.persist.model.SecondaryKey;
//声明一个实体类; 即具有主索引和可选的一个或多个索引的类。
@Entity
public class User {
//主键 如果插入时不指定ID 就是插入,指定ID如果存在就插入已存在就是修改
//通过指定要用于主键的序列来避免需要为类的主索引显式设置值。 这会导致一个唯一的整数值被用作每个存储对象的主键。
@PrimaryKey(sequence = "ID")
private Long userId;
//辅助键
@SecondaryKey(relate = Relationship.MANY_TO_ONE)
private String userName;
private String password;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
public User(Long userId, String userName, String password) {
super();
this.userId = userId;
this.userName = userName;
this.password = password;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return super.toString();
}
}
1.2.2 数据库持久层操作类
UserDA.java
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;
import model.User;
public class UserDA {
private final static Logger logger = LoggerFactory.getLogger(UserDA.class);
// 主键字段类型,实体类
PrimaryIndex<Long, User> pIdx;// 一级索引
// 辅助键字段类型,主键字段类型,实体类
SecondaryIndex<String, Long, User> sIdx;// 二级索引
public UserDA(EntityStore entityStore) {
// 主键字段类型,实体类
pIdx = entityStore.getPrimaryIndex(Long.class, User.class);
// 主键索引,辅助键字段类型,辅助键字段名称
sIdx = entityStore.getSecondaryIndex(pIdx, String.class, "userName");
}
/**
* 添加一个User
*/
public void saveUser(User user) {
pIdx.put(user);
}
/**
* 根据用户Id删除一个User
**/
public void removedUserById(Long userId) {
pIdx.delete(userId);
}
/**
* 根据用户名称删除一个User
**/
public void removedUserByUserName(String userName) {
sIdx.delete(userName);
}
/**
* 根据用户Id修改单个用户
**/
public User modifyUserById(User user) {
User modifyUser=null;
modifyUser=pIdx.get(user.getUserId());
modifyUser.setUserName(user.getUserName());
modifyUser.setPassword(user.getPassword());
return modifyUser;
}
/**
* 根据用户Id查找一个User
**/
public User findUserById(Long userId) {
return pIdx.get(userId);
}
/**
* 查找所有的User
**/
public List<User> findAllUser() {
List<User> userList = new ArrayList<User>();
// 打开游标
EntityCursor<User> userCursorList = null;
try {
//获取游标
userCursorList = pIdx.entities();
// 遍历游标
for (User user : userCursorList) {
userList.add(user);
}
} catch (DatabaseException e) {
// TODO Auto-generated catch block
logger.error(e.toString());
} finally {
if (userCursorList != null) {
// 关闭游标
userCursorList.close();
}
}
return userList;
}
/**
* 根据userName查找所有的User
**/
public List<User> findAllUserByUserName(String userName) {
List<User> userList=new ArrayList<User>();
EntityCursor<User> entityCursorList=null;
//获取游标
try {
entityCursorList=sIdx.subIndex(userName).entities();
//遍历游标
for (User user : entityCursorList) {
userList.add(user);
}
} catch (DatabaseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(entityCursorList!=null) {
//关闭游标
entityCursorList.close();
}
}
return userList;
}
/**
* 统计所有用户数
**/
public Long findAllUserCount() {
Long count = 0L;
EntityCursor<User> cursor = null;
try{
cursor = pIdx.entities();
for (User user : cursor) {
if(user!=null) {
count++;
}
}
}finally {
if(cursor != null){
cursor.close();
}
}
return count;
}
/**
* 统计所有满足用户名的用户总数
*****/
public Long findAllUserByUserNameCount(String userName) {
Long count = 0L;
EntityCursor<User> cursor = null;
try{
cursor = sIdx.subIndex(userName).entities();
for (User user : cursor) {
if(user!=null) {
count++;
}
}
}finally {
if(cursor != null){
cursor.close();
}
}
return count;
}
}
1.2.3 主方法调用
import java.io.File;
import java.util.List;
import dao.UserDA;
import model.User;
import util.BDBEnvironmentManager;
public class MainTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//打开数据库和存储环境
BDBEnvironmentManager.getInstance(new File("bdb"),false);
UserDA userDA=new UserDA(BDBEnvironmentManager.getMyEntityStore());
userDA.saveUser(new User(1L,"A","root1"));
userDA.saveUser(new User(2L,"admin","root2"));
userDA.saveUser(new User(3L,"A","root3"));
userDA.saveUser(new User(4L,"admin","root4"));
System.out.println(userDA.findAllUserCount());
System.out.println(userDA.findAllUserByUserNameCount("admin"));
printAllDataByUserName(userDA,"admin");
printAllData(userDA);
userDA.removedUserById(2L);
printAllData(userDA);
userDA.removedUserByUserName("admin");
printAllData(userDA);
userDA.saveUser(new User(1L,"admin","root1"));
printAllData(userDA);
BDBEnvironmentManager.getMyEnvironment().sync();
BDBEnvironmentManager.close();
}
private static void printAllData(UserDA userDA) {
// TODO Auto-generated method stub
System.out.println("------start--------");
List<User> userList=userDA.findAllUser();
for (User user : userList) {
System.out.println(user.getUserId());
System.out.println(user.getUserName());
System.out.println(user.getPassword());
}
System.out.println("------end--------");
}
private static void printAllDataByUserName(UserDA userDA,String userName) {
// TODO Auto-generated method stub
System.out.println("------start--------");
List<User> userList=userDA.findAllUserByUserName(userName);
for (User user : userList) {
System.out.println(user.getUserId());
System.out.println(user.getUserName());
System.out.println(user.getPassword());
}
System.out.println("------end--------");
}
}
1.2.4 输出结果
输出结果如下:
4
2
------start--------
2
admin
root2
4
admin
root4
------end--------
------start--------
1
A
root1
2
admin
root2
3
A
root3
4
admin
root4
------end--------
------start--------
1
A
root1
3
A
root3
4
admin
root4
------end--------
------start--------
1
A
root1
3
A
root3
------end--------
------start--------
1
admin
root1
3
A
root3
------end--------
执行上面的代码后可能你会发现,无论执行多少次,结果一直不变。
这是为什么呢?
这是因为在JE中,saveUser(User user);
方法不止是插入也是修改。我们每次插入数据,都用同一个ID的话,那么插入方法就变成了修改。
还记得我们之前在实体类中设置了自增长么?
@PrimaryKey(sequence = "ID")
private Long userId;
我们的构造方法写了三个
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
public User(Long userId, String userName, String password) {
super();
this.userId = userId;
this.userName = userName;
this.password = password;
}
现在我们改写下调用没有ID的构造方法
userDA.saveUser(new User("A","root1"));
userDA.saveUser(new User("admin","root2"));
userDA.saveUser(new User("A","root3"));
userDA.saveUser(new User("admin","root4"));
完整主方法调用代码如下:
import java.io.File;
import java.util.List;
import dao.UserDA;
import model.User;
import util.BDBEnvironmentManager;
public class MainTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//打开数据库和存储环境
BDBEnvironmentManager.getInstance(new File("bdb"),false);
UserDA userDA=new UserDA(BDBEnvironmentManager.getMyEntityStore());
userDA.saveUser(new User("A","root1"));
userDA.saveUser(new User("admin","root2"));
userDA.saveUser(new User("A","root3"));
userDA.saveUser(new User("admin","root4"));
System.out.println(userDA.findAllUserCount());
System.out.println(userDA.findAllUserByUserNameCount("admin"));
printAllDataByUserName(userDA,"admin");
printAllData(userDA);
userDA.removedUserById(2L);
printAllData(userDA);
userDA.removedUserByUserName("admin");
printAllData(userDA);
userDA.saveUser(new User(1L,"admin","root1"));
printAllData(userDA);
BDBEnvironmentManager.getMyEnvironment().sync();
BDBEnvironmentManager.close();
}
private static void printAllData(UserDA userDA) {
// TODO Auto-generated method stub
System.out.println("------start--------");
List<User> userList=userDA.findAllUser();
for (User user : userList) {
System.out.println(user.getUserId());
System.out.println(user.getUserName());
System.out.println(user.getPassword());
}
System.out.println("------end--------");
}
private static void printAllDataByUserName(UserDA userDA,String userName) {
// TODO Auto-generated method stub
System.out.println("------start--------");
List<User> userList=userDA.findAllUserByUserName(userName);
for (User user : userList) {
System.out.println(user.getUserId());
System.out.println(user.getUserName());
System.out.println(user.getPassword());
}
System.out.println("------end--------");
}
}
如果插入后想马上将内存中的缓存写入到文件,那么当调用插入后,立即执行这条语句:
BDBEnvironmentManager.getMyEnvironment().sync();
代码中有详细注释,不懂的地方或哪里有不对之处,欢迎留言区参与讨论