4.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();

代码中有详细注释,不懂的地方或哪里有不对之处,欢迎留言区参与讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

极客星云

谢谢认可,希望对你的学习有帮助

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

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

打赏作者

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

抵扣说明:

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

余额充值