mirai-console 插件使用 Hibernate-JPA 的 方式 调用数据库
-
JPA是Java Persistence API的简称,中文名Java持久层API,
可以通过 注解或XML描述对象-关系表的映射关系。简单来说,你声明了一个实体类,可以通过注解的方式,实现
- 将注解类和数据库中是数据表关联,一一对应
- 将实体类的属性和数据列关联,一一对应
- 将实体类和实体类关联,外键
以上功能 sql 将会自动生成,而不需要手写 sql 。
jpa 这种方式有利有弊。相对于 mybatis 来说,隐去了维护 sql 的麻烦,方便支持多数据库平台,
缺点是性能差,不利于像 mybatis 一样可以通过插件拓展功能。
但是一般来说,mirai-console 插件 并不需要过多关注数据库性能。可作为前置插件的成品,带有一个消息记录器持久化消息到数据库
https://github.com/cssxsh/mirai-hibernate-plugin首先在 dependencies 中加入相关依赖
从我的前置插件中获得dependencies { implementation("xyz.cssxsh.mirai:mirai-hibernate-plugin:2.4.4") } mirai { jvmTarget = JavaVersion.VERSION_11 }
或者你可以从原始库中获得
dependencies { // SQL/ORM api("org.hibernate.orm:hibernate-core:6.1.3.Final") api("org.hibernate.orm:hibernate-hikaricp:6.1.3.Final") api("org.hibernate.orm:hibernate-community-dialects:6.1.3.Final") // 连接池 api("com.zaxxer:HikariCP:5.0.1") // 数据库驱动 api("com.h2database:h2:2.1.214") api("org.xerial:sqlite-jdbc:3.39.3.0") api("mysql:mysql-connector-java:8.0.30") api("org.postgresql:postgresql:42.5.0") testImplementation(kotlin("test")) testImplementation("org.slf4j:slf4j-simple:2.0.0") testImplementation("net.mamoe:mirai-logging-slf4j:2.12.3") testImplementation("net.mamoe:mirai-core-utils:2.12.3") } mirai { jvmTarget = JavaVersion.VERSION_11 }
-
然后初始化 hibernate 配置
org.hibernate.cfg.Configuration这里,为了方便用户修改,我们使用从插件数据目录加载文件配置的方式
data/xxxxx/hibernate.properties
通过修改以下几个配置项可以调整插件使用的数据库hibernate.connection.url hibernate.connection.driver_class hibernate.dialect
你可以使用我的前置插件中提供的 MiraiHibernateConfiguration
MiraiHibernateConfiguration 会自动扫描entry
,entity
,entities
,model
,models
,bean
,beans
,dto
包中的类
但只会扫描一个包,先发现那个包就扫那个包
并且会自动生成 h2 数据库 的 hibernate.properties 文件
javavar configuration = new MiraiHibernateConfiguration(this);
kotlin
val configuration = MiraiHibernateConfiguration(plugin = this)
hibernate.properties 文件内容示例
hibernate.connection.url=jdbc:h2:file:./data/xxxxx/hibernate.properties hibernate.connection.driver_class=org.h2.Driver hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.connection.provider_class=org.hibernate.hikaricp.internal.HikariCPConnectionProvider hibernate.hikari.connectionTimeout=180000 hibernate.connection.isolation=1 hibernate.hbm2ddl.auto=update hibernate-connection-autocommit=true hibernate.connection.show_sql=false hibernate.autoReconnect=true
或者你可以使用原生的加载方式
java
@Override public void onEnable() { Configuration configuration = new Configuration(); // 加载用户配置 try (InputStream in = new FileInputStream(resolveDataFile("hibernate.properties"))) { configuration.getProperties().load(in); } catch (Exception e) { getLogger().error(e); } // 添加 实体类 configuration.addAnnotatedClass(User.class); }
kotlin
override fun onEnable() { val configuration = Configuration() // 加载用户配置 resolveDataFile("hibernate.properties").inputStream() .use { `in` -> configuration.properties.load(`in`) } // 添加 实体类 configuration.addAnnotatedClass(User::class.java) }
-
实体类的示例
java
@Entity @Table(name = "user") public class User { @Id private Long id; private String name; @OneToMany(mappedBy = "user") private List<Work> works; public void setId(Long id) { this.id = id; } public Long getId() { return id; } public void setName(String name) { this.name = name; } public String getName() { return name; } public List<Work> getWorks() { return works; } public void setWorks(List<Work> works) { this.works = works;} }
@Entity @Table(name = "work") public class Work { @Id private Long pid; private String content; @ManyToOne private User user; public Long getPid() { return pid; } public void setPid(Long pid) { this.pid = pid; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
kotlin
@Entity @Table(name = "user") public data class User( val id: Long, val name: String, @OneToMany(mappedBy = "user") val works: List<Work> )
@Entity @Table(name = "user") public data class Work( val pid: Long, val content: String, @ManyToOne val user: User )
-
创建一个 SessionFactory 并使用
注意,对于一个插件来说,factory 应该在 onEnable 中创建,onDisable 中关闭。并且不对一个数据库创建多个 factory。
java
var factory = configuration.buildSessionFactory(); // 用这个方法能安全关闭 Session 不需要额外写 try catch var u = factory.fromSession((session) -> { // 查找 return session.find(User.class, 1L); }); // 用这个方法能安全关闭 Transaction 不需要额外写 try catch // fromTransaction 在 fromSession 的基础上套了一层 事务(Transaction) // 当有数据变更时,必须套着事务 factory.fromTransaction((session) -> { // 插入 var user = new User(); user.setId(1L); user.setName("name"); session.persist(user); // 修改 user.setName("new name"); session.merge(user); // 删除 session.remove(user); // 原生sql 查询 session.createNativeQuery("select * from user", User.class) .list(); // hql 查询(格式像是 sql 混杂 java) String hql = "from User s where s.name = :name"; session.createQuery(hql, User.class) .setParameter("name", "...") .list(); String hql2 = "from Work w where w.user.name = :name"; session.createQuery(hql2, Work.class) .setParameter("name", "...") .list(); // criteria 查询(纯 java 代码的方式构造 sql)我推荐这种,不过用起来比较复杂 var builder = session.getCriteriaBuilder(); var query = builder.createQuery(User.class); var root = query.from(User.class); query.select(root); query.where(builder.between(root.get("id"), 0L, 1000L)); var list = session.createQuery(query) .list(); var query2 = builder.createQuery(Work.class); var root2 = query.from(Work.class); query2.select(root2); query2.where(builder.between(root2.get("user").get("id"), 0L, 1000L)); var list2 = session.createQuery(query) .list(); return 0; });
-
顺带一提
mirai-hibernate-plugin 中提供了消息记录器xyz.cssxsh.mirai.hibernate.MiraiHibernateRecorder
, 并且已经注册到mirai-hibernate-plugin,可以直接使用MiraiHibernateRecorder 提供多个 get 函数 ,可以使用
MessageSource
.Bot
,Member
等作为参数 获得历史消息例子
kotlin// 返回一个流,请记得关闭这个流 MiraiHibernateRecorder[subject].use { steam -> steam.forEach { record -> // 转化成消息链 record.toMessageChain() // 转化消息引用 // 这里的 originalMessage 来自 上面的 toMessageChain record.toMessageSource().originalMessage } } // 返回一个列表,第 2,3 参数是 开始时刻和结束时间 MiraiHibernateRecorder[subject, 16000000, 160000000].forEach { record -> // 转化成消息链 record.toMessageChain() // 转化消息引用 // 这里的 originalMessage 来自 上面的 toMessageChain record.toMessageSource().originalMessage } }
java
// 返回一个流,请记得关闭这个流 try (var steam = MiraiHibernateRecorder.INSTANCE.get(Bot.findInstance(123456))) { MessageRecord record = steam.findFirst().get(); // 转化成消息链 MessageChain message = record.toMessageChain(); // 转化消息引用 // 这里的 originalMessage 来自 上面的 toMessageChain MessageSource source = record.toMessageSource(); } catch (Exception e) { // } // 返回一个列表,第 2,3 参数是 开始时刻和结束时间 var list = MiraiHibernateRecorder.INSTANCE.get(Bot.getInstance(123456), 1600000, 1600001); for (MessageRecord record : list) { record.toMessageSource(); // or record.toMessageChain(); }
-
接触JPA三个过程:
初识,诶,挺好用的,不用写sql语句了,
深入使用,嗯?怎么我这种复杂的的动态查询sql语句没办法实现,
结局,回归原始mybatis -
一般来说是可以的
只不过会因为和实体类直接绑定显得很蹩脚? -
@cssxsh 大佬,我想问下,为啥装上插件后会显示disabled,需要怎么开启呢 -
-
@cssxsh 大佬,这日志我也看不懂啊,找不到问题
-
-
@cssxsh 没事了,我重新安装了插件就不会disabled了