在 Room 数据库中使用事务处理联表操作

首先是错误版本的相关代码内容:

Event.kt:

import androidx.room.Entity
import androidx.room.PrimaryKey@Entity(tableName = "events")
data class Event(val title: String,val description: String,val timestamp: Long,@PrimaryKey(autoGenerate = true) val eventId: Long = 0L
)

EventDao.kt:

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query@Dao
interface EventDao {@Insertsuspend fun insertEvent(event: Event): Long@Query("SELECT * FROM events WHERE eventId = :id")suspend fun getEventById(id: Long): Event@Deletesuspend fun deleteEvent(event: Event)@Insertsuspend fun insertAttendees(attendees: List<Attendee>)
}

Attendee.kt

import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey@Entity(tableName = "attendees",foreignKeys = [ForeignKey(entity = Event::class,parentColumns = ["eventId"],childColumns = ["eventId"],onDelete = ForeignKey.CASCADE)]
)
data class Attendee(val eventId: Long,val name: String,val profilePictureUrl: String?,@PrimaryKey(autoGenerate = true) val attendeeId: Long = 0L
)

AttendeeDao.kt

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.plcoding.roomtransactions.Attendee@Dao
interface AttendeeDao {@Insertsuspend fun insertAttendee(attendee: Attendee)@Query("SELECT * FROM attendees WHERE eventId = :eventId")suspend fun getAttendeesForEvent(eventId: Long): List<Attendee>@Deletesuspend fun deleteAttendee(attendee: Attendee)
}

EventWithAttendees.kt

import androidx.room.Embedded
import androidx.room.Relation
import com.plcoding.roomtransactions.Attendee
import com.plcoding.roomtransactions.Eventdata class EventWithAttendees(@Embedded val event: Event,@Relation(parentColumn = "eventId",entityColumn = "eventId")val attendees: List<Attendee>
)

AppDatabase.kt

import androidx.room.Database
import androidx.room.RoomDatabase@Database(entities = [Event::class, Attendee::class], version = 1)
abstract class AppDatabase : RoomDatabase() {abstract fun eventDao(): EventDaoabstract fun attendeeDao(): AttendeeDao
}

AppModule.kt

import android.app.Application
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideAppDatabase(app: Application): AppDatabase {return Room.databaseBuilder(app, AppDatabase::class.java, "app.db").build()}
}

以上代码中 Event 实体和 Attendee 实体之间通过EventWithAttendees定义了一对多的关系。

更多关于 Room 数据库中对象与对象之间的关系定义,请参考Jetpack架构组件库:Room。

MainViewModel.kt

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject@HiltViewModel
class MainViewModel @Inject constructor(private val db: AppDatabase
): ViewModel() {fun insertEvent() {viewModelScope.launch {val event = Event(title = "Test event",description = "My event",timestamp = System.currentTimeMillis())val eventId = db.eventDao().insertEvent(event)val attendees = (1..10).map {Attendee(eventId = eventId,name = "Test attendee$it",profilePictureUrl = null)}attendees.forEach {db.attendeeDao().insertAttendee(it)}}}
}

MainActivity.kt

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.plcoding.roomtransactions.ui.theme.RoomTransactionsTheme
import dagger.hilt.android.AndroidEntryPoint@AndroidEntryPoint
class MainActivity : ComponentActivity() {private val viewModel by viewModels<MainViewModel>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {RoomTransactionsTheme {Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Button(onClick = {viewModel.insertEvent()}) {Text(text = "Insert event")}}}}}
}

App.kt

import android.app.Application
import dagger.hilt.android.HiltAndroidApp@HiltAndroidApp
class App: Application()

MainViewModel 中先向event表中插入event对象,拿到 eventId 构造出 attendees 对象,再插入到 Attendee 表中。

这里的主要问题是下面三部分代码:

val eventId = db.eventDao().insertEvent(event)
val attendees = (1..10).map {Attendee(eventId = eventId,name = "Test attendee$it",profilePictureUrl = null)
}        
attendees.forEach {db.attendeeDao().insertAttendee(it)
}

这三块代码的操作不是原子性的,假设第一块代码向数据库插入成功了,但是在跑第二块代码时应用崩溃了,第三块代码就没有执行,从而导致bug。同样的,如果第一块代码和第二块代码执行成功了,但是第三块代码执行失败了,前面部分的操作也不会回滚,从而导致bug。

正确的处理方法是使用 Room 数据库中的@Transaction事务来处理多个数据库操作:

修改EventDao.kt内容如下:

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow@Dao
interface EventDao {@Insertsuspend fun insertEvent(event: Event): Long@Query("SELECT * FROM events WHERE eventId = :id")suspend fun getEventById(id: Long): Event@Deletesuspend fun deleteEvent(event: Event)@Insertsuspend fun insertAttendees(attendees: List<Attendee>)@Transactionsuspend fun insertEventWithAttendees(event: Event, attendees: List<Attendee>) {val eventId = insertEvent(event)val attendeesWithEventId = attendees.map {it.copy(eventId = eventId)}insertAttendees(attendeesWithEventId)}
}

这里主要增加了insertEventWithAttendees方法,并为其添加@Transaction注解,在其中执行相关的表操作。这样表示insertEventWithAttendees方法中对数据库表的操作,要么全部成功,要么全部失败,即便在中间某个操作应用崩溃了,也不会只执行了一半,即保证事务是原子性的。

然后修改MainViewModel.kt内容如下:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject@HiltViewModel
class MainViewModel @Inject constructor(private val db: AppDatabase
): ViewModel() {fun insertEvent() {viewModelScope.launch {val event = Event(title = "Test event",description = "My event",timestamp = System.currentTimeMillis())val attendees = (1..10).map {Attendee(eventId = 0,name = "Test attendee$it",profilePictureUrl = null)}db.eventDao().insertEventWithAttendees(event, attendees)}}
}

这里只需要调用上面定义的事务方法即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/707282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下性能分析的可视化图表工具

1 sar 和sadf 1.1 简介 sar命令可以记录系统下的常见活动信息&#xff0c;例如CPU使用率、网络统计数据、Block I/O数据、内存使用情况 等。 sar命令的“-o [file_name]”参数可以将系统活动数据记录到file_name文件&#xff0c;然后通过sadf来解析&#xff0c;sadf命令的“-g…

SpringMVC 学习(八)之文件上传与下载

目录 1 文件上传 2 文件下载 1 文件上传 SpringMVC 对文件的上传做了很好的封装&#xff0c;提供了两种解析器。 CommonsMultipartResolver&#xff1a;兼容性较好&#xff0c;可以兼容 Servlet3.0 之前的版本&#xff0c;但是它依赖了 commons-fileupload …

CW023A-H035 CW023A-R230铜合金硬度材质书

CW023A-H035 CW023A-R230铜合金硬度材质书C2100W-O、C2680TWS-OL、C2200W-1/2H、C2800TS-O 、C2800T-H、C2800T-1/2H、C2700TS-O、C4430T-O、C2700T-O、C2700T-H、C2700T-OL、C2800TS-1/2H、C2800T-OL、C2700-O、C2800TS-H、C2700-H、C2700T-1/2H、C2600TS-1/2H、C2700-1/2H、C…

1817735-38-8,羧基五 PEG5 磺酸,含有羧酸和磺酸的PEG连接剂

1817735-38-8&#xff0c;Carboxy-PEG5-sulfonic acid&#xff0c;羧基五聚乙二醇磺酸&#xff0c;羧基五 PEG5 磺酸&#xff0c;含有羧酸和磺酸的PEG连接剂&#xff0c;可以进行酯化反应 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;1817735-38-8&#xff0c;…

鸿蒙媒体开发【简述】

媒体系统架构 媒体系统提供用户视觉、听觉信息的处理能力&#xff0c;如音视频信息的采集、压缩存储、解压播放等。在操作系统实现中&#xff0c;通常基于不同的媒体信息处理内容&#xff0c;将媒体划分为不同的模块&#xff0c;包括&#xff1a;音频、视频&#xff08;也称播…

pycharm配置环境出现unsupported

情况概述&#xff1a; 本人电脑中的pycharm版本是2019的&#xff0c;在使用python3.10环境的时候&#xff0c;pycharm无法识别&#xff0c;出现如下错误&#xff1a; 网上说是因为python版本过高&#xff0c;无法兼容低版本的pycharm&#xff0c;解决方案分两种&#xff1a;要…

js中和Vue中的事件委托(事件代理)的实现方法

目录 一、事件委托&#xff08;事件代理&#xff09; 1、事件委托的优点 2、事件委托的缺点 3、为什么要使用事件委托 4、事件委托实现原理 5、DOM事件流 6、事件委托的实现方法 1、vue写法 1.1、html代码 1.2、js代码 2、原生的写法其实也差不多&#xff1a; 2.1、…

redis中的分布式锁(setIfAbsent)(expire)

目录 应用场景 代码实例1&#xff1a; 代码实例2&#xff1a; setIfAbsent&#xff1a; expire&#xff1a; 举例说明&#xff1a; 代码实例3&#xff1a; 代码实例4&#xff1a; 还是一个同事问的一个问题&#xff0c;然后闲着没事就记录下来了。多人操作同一个保单&a…

全量知识系统问题及SmartChat给出的答复 之2

Q6. 根据DDD的思想( 也就是借助 DDD的某个或某些实现)&#xff0c;是否能按照这个想法给出程序设计和代码结构&#xff1f; 当使用领域驱动设计&#xff08;DDD&#xff09;的思想来设计程序和代码结构时&#xff0c;可以根据领域模型、领域服务、值对象、实体等概念来进行设计…

全网最详细的接口自动化测试框架实战(Pytest+Allure+Excel)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. Allure 简介 Allure 框架是一个灵活的、轻量级的、支持多语…

k8s service的概念以及创建方法

Service 的功能&#xff1a; Service主要用于提供网络服务&#xff0c;通过Service的定义&#xff0c;能够为客户端应用提供稳定的访问地址&#xff08;域名或IP地址&#xff09;和负载均衡功能&#xff0c;以及屏蔽后端Endpoint的变化&#xff0c;是K8s实现微服务的核心资源。…

Android 13 - Media框架(32)- ACodec(八)

拖了好久都没有更新&#xff0c;前面写的东西都有些忘了&#xff0c;回过头来再看之前写的内容&#xff0c;觉得有很多地方写的不好&#xff0c;或者说现在又有了新的理解&#xff0c;想要重新修改但是需要修改的内容太多&#xff0c;因此决定按照当前的思路把剩余的内容写完。…

密码学——二次剩余

引言 二次剩余在许多密码学算法和数论问题中具有重要的作用,这个概念涉及到同余方程等概念。 同余 学习二次剩余的概念,首先要了解同余方程的概念,首先默认研究范围为整数,假设 f ( x ) f(x) f(x)是一个整系数多项式,我们讨论是否有整数值x满足同余式 f (

(已解决)mac中全局安装express-generator后,爆出zsh: command not found: express

问题描述&#xff1a; 在mac中全局安装express-generator后&#xff0c;在终端输入express命令提示&#xff1a;zsh: command not found: express 原因分析&#xff1a; 因为系统没有找到express命令的位置。 解决步骤&#xff1a; 检查是否正确安装了express-generator&…

react-JSX基本使用

1.目标 能够知道什么是JSX 能够使用JSX创建React元素 能够在JSX中使用JS表达式 能够使用JSX的条件渲染和列表渲染 能够给JSX添加样式 2.目录 JSX的基本使用 JSX中使用JS表达式 JSX的条件渲染 JSX的列表渲染 JSX的样式处理 3.JSX的基本使用 3.1 createElement()的问题 A. …

Git - 开启2FA

Refrence: [Github实战]双重认证2FA 如何 设置/更改[手把手][2022]_enable two-factor authentication (2fa)-CSDN博客

在Node.js中如何实现用户身份验证和授权

当涉及到构建安全的应用程序时&#xff0c;用户身份验证和授权是至关重要的一环。在Node.js中&#xff0c;我们可以利用一些流行的库和技术来实现这些功能&#xff0c;确保我们的应用程序具有所需的安全性。本篇博客将介绍如何在Node.js中实现用户身份验证和授权。 用户身份验…

“智农”-大棚可视化

基于自主可控的数字孪生技术、物联网技术、大数据技术&#xff0c;构建全流程的新型农业一体化管理平台&#xff0c;围绕产运销管理全流程&#xff0c;实现生产->存储->包装->运输->销售的全链条管理。融合农业数据管理、农业数据预警显示、多维数据综合显示、农产…

Jvm之内存泄漏

1 内存溢出 1.1 概念 java.lang.OutOfMemoryError&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现OutOfMemoryError。产生该错误的原因主要包括&#xff1a;JVM内存过小。程序不严密&#xff0c;产生了过多的垃圾。 程序体现: 内…

微服务架构 SpringCloud

单体应用架构 将项目所有模块(功能)打成jar或者war&#xff0c;然后部署一个进程--医院挂号系统&#xff1b; > 优点: > 1:部署简单:由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可。 > 2:技术单一:项目不需要复杂的技术栈&#xff0c;往往一套熟悉的…