Room数据库高级玩法:用@Ignore注解实现业务逻辑与数据层分离

Matlab2018a崩溃解决的办法No sandbox or build area path Matlab 2018a (No sandbox or build area path)出现崩溃后需要使用terminal进行如下操作: cd /home/david/MySoft/MATLAB/R2018a/bin/glnxa64 (你的matlab路径) mkdir exclude mv libfreetype* exclude/ 执行后图片如下: 如果你用的Windows系统,请手动按照上面步骤剔除了dll。 Matlab 2018a (No sandbox or build are 阅读详情

Room数据库高级玩法:用@Ignore注解实现业务逻辑与数据层分离

在Android开发中,Room作为Jetpack组件库中的官方ORM框架,早已成为处理本地数据持久化的首选。大多数开发者对它的认知停留在“定义Entity、创建Dao、构建Database”这三板斧上,用它来替代繁琐的SQLiteOpenHelper和Cursor操作。然而,当我们深入复杂业务场景时,往往会发现一个尴尬的局面:数据模型(Entity)为了适配数据库表结构,不得不塞入各种仅用于查询的冗余字段,或者为了满足业务逻辑的需要,在Entity类中添加了大量与持久化无关的属性和方法,导致类职责混乱,变得臃肿不堪。

这正是@Ignore注解可以大显身手的地方。这个看似简单的注解,其价值远不止于“让某个字段不被持久化”。它实际上是一把精巧的手术刀,能够精准地将业务逻辑模型与持久化数据模型进行切割。通过创造性地使用@Ignore,我们可以让Entity回归其本质——纯粹的数据表结构映射,而将复杂的业务计算、临时状态、视图绑定数据等剥离出去,从而实现更清晰、更灵活、更易维护的架构设计。本文将带你跳出基础用法的窠臼,探索@Ignore在实现关注点分离、提升代码可测试性以及适配多场景初始化等方面的进阶实践。

1. 重新审视@Ignore:不止于“忽略字段”

在Room的官方文档中,@Ignore注解的说明非常简洁:用于标记不需要持久化到数据库的字段或构造函数。这种描述容易让人将其功能狭隘化。实际上,@Ignore是Room框架留给开发者的一个重要扩展点和设计工具。

1.1 @Ignore的核心机制与设计意图

要理解@Ignore的高级用法,首先需要明白Room在编译期的工作原理。Room的注解处理器(room-compiler)会扫描所有被@Entity标记的类,并为其生成对应的实现代码。这个过程主要包括:

  1. 表结构生成:根据未被@Ignore标记的字段及其@ColumnInfo配置,生成创建表的SQL语句。
  2. 数据绑定逻辑生成:为@Insert、@Update等操作生成将对象属性绑定到SQL语句参数(如ContentValues或SupportSQLiteStatement)的代码。关键点在于,只有未被@Ignore的字段才会参与这个绑定过程。
  3. 对象实例化支持:Room需要知道如何从数据库查询结果(Cursor)中构造出Entity对象。它会识别Entity类中可用的构造函数。如果只有一个构造函数,Room会使用它;如果有多个,Room默认会使用那个未被@Ignore标记的构造函数。
// 一个典型的Entity,混合了持久化字段和业务字段
@Entity(tableName = "articles")
data class Article(
    @PrimaryKey
    val id: Long,
    val title: String,
    val content: String,
    val publishTime: Long,
    val authorId: Long,

    // 以下字段仅用于UI或业务逻辑,不应存入数据库
    @Ignore
    val authorName: String = "", // 需要联表查询或从其他服务获取
    @Ignore
    val isFavorited: Boolean = false, // 用户个人状态,与文章本身无关
    @Ignore
    val formattedPublishTime: String = "" // 由publishTime计算得出,属于展示逻辑
)

上面的Article类揭示了基础用法的一个问题:虽然我们用@Ignore分离了字段,但它们仍然混在同一个类里。当我们需要一个仅包含持久化字段的“纯净”对象用于网络序列化或深度拷贝时,这个类就显得不那么纯粹了。更高级的玩法,是让@Ignore帮助我们实现模型的分层。

1.2 利用@Ignore构造函数实现模型转换

@Ignore标记构造函数的功能常被忽视。它允许我们为Entity提供多种构建方式,同时明确告知Room:“在从数据库还原对象时,请不要使用这个构造器”。这为领域模型(Domain Model)与持久化模型(Persistence Model)的转换打开了大门。

考虑一个电商场景的商品Product实体。领域层关注的可能是商品的价格、库存、规格等业务属性;而持久层存储的可能是拆解后的更细粒度的数据。

// 持久化模型 - 对应数据库表
@Entity(tableName = "products")
data class ProductEntity(
    @PrimaryKey
    val sku: String,
    @ColumnInfo(name = "base_price")
    val basePriceInCents: Int, // 以分为单位存储,避免浮点数精度问题
    @ColumnInfo(name = "currency")
    val currencyCode: String,
    val stock: Int,
    @ColumnInfo(name = "spec_json") // 规格信息以JSON字符串存储
    val specificationsJson: String
) {
    // Room用于从数据库加载的构造器
    constructor(
        sku: String,
        basePriceInCents: Int,
        currencyCode: String,
        stock: Int,
        specificationsJson: String
    ) : this(sku, basePriceInCents, currencyCode, stock, specificationsJson)

    // 被@Ignore的构造器:用于从领域对象转换而来
    @Ignore
    constructor(domainProduct: Product) : this(
        sku = domainProduct.sku,
        basePriceInCents = (domainProduct.price.amount * 100).toInt(),
        currencyCode = domainProduct.price.currency,
        stock = domainProduct.inventory.availableStock,
        specificationsJson = Gson().toJson(domainProduct.specifications)
    )
}

// 领域模型 - 包含业务逻辑
data class Product(
    val sku: String,
    val price: Money, // 自定义值对象,包含金额和货币
    val inventory: Inventory, // 值对象,包含可用库存、在途库存等
    val specifications: Map<String, String>
) {
    // 业务逻辑方法,例如计算折扣价
    fun calculateDiscountedPrice(discountRate: Double): Money {
        return price.copy(amount = price.amount * (1 - discountRate))
    }
}

提示:这种模式遵循了依赖反转原则。持久化模型(ProductEntity)知晓如何从领域模型(Product)构建自身,但领域模型对持久化细节一无所知,保持了独立性和可测试性。

通过@Ignore构造函数,我们实现了从丰富的领域对象到扁平化数据库记录的优雅转换。Dao层操作ProductEntity,而业务层始终使用Product对象。两者的转换可以封装在Repository层:

class ProductRepository @Inject constructor(
    private val productDao: ProductDao,
    private val mapper: ProductMapper // 可选的,使用Mapper模式进一步解耦
) {
    suspend fun getProduct(sku: String): Product {
        val entity = productDao.getBySku(sku)
        // 将Entity转换回Domain Model(这里需要额外的fromEntity方法或使用Mapper)
        return entity.toDomain()
    }

    suspend fun saveProduct(product: Product) {
        // 利用@Ignore构造函数直接创建Entity
        val entity = ProductEntity(product)
        productDao.insertOrUpdate(entity)
    }
}

2. 实战:MVVM架构下的关注点分离

MVVM(Model-View-ViewModel)架构强调数据驱动UI。这里的“Model”通常指代领域模型或业务逻辑,但在Android开发中,由于Room的存在,“Model”层常常与数据库Entity纠缠不清。@Ignore可以帮助我们理清这层关系。

2.1 视图状态(ViewState)与持久化实体的分离

在MVVM中,ViewModel负责准备用于UI显示的数据。这些数据往往是多个实体(Entity)的组合、加工后的结果。一个常见的错误是让Entity直接承载这些UI状态。

假设我们有一个任务管理应用,TaskEntity代表数据库中的任务。在UI上,我们可能需要显示:

  • 任务基本信息(来自TaskEntity)
  • 任务是否已过期(需要根据当前时间与截止时间计算)
  • 任务优先级对应的颜色标签(映射关系)
  • 分配给当前用户的任务子项数量(需要关联查询)
// 反例:将UI状态混入Entity
@Entity(tableName = "tasks")
data class TaskEntity(
    @PrimaryKey val id: Long,
    val title: String,
    val description: String?,
    val dueDate: Long, // 时间戳
    val priority: Int, // 1=低, 2=中, 3=高
    val assigneeId: Long?,
    // 以下全是UI状态,污染了Entity
    @Ignore
    var isOverdue: Boolean = false,
    @Ignore
    var priorityColor: Int = Color.GRAY,
    @Ignore
    var assignedSubTaskCount: Int = 0
)

虽然用了@Ignore,但TaskEntity依然承担了过多职责。更好的做法是创建独立的ViewState数据类,专门用于UI展示。

// 纯净的持久化实体
@Entity(tableName = "tasks")
data class TaskEntity(
    @PrimaryKey val id: Long,
    val title: String,
    val description: String?,
    val dueDate: Long,
    val priority: Int,
    val assigneeId: Long?,
    val projectId: Long,
    val createdAt: Long
) {
    // 提供一个@Ignore构造函数,用于从领域对象或网络模型转换(如果需要)
}

// 独立的UI状态类
data class TaskViewState(
    val id: Long,
    val title: String,
    val description: String?,
    val dueDate: Long,
    val priority: Int,
    // 衍生状态
    val isOverdue: Boolean,
    val priorityColorResId: Int, // 资源ID,便于在XML中引用
    val assigneeName: String?,
    val subTaskSummary: String // 如“3/5完成”
) {
    // 可包含UI相关的格式化方法
    fun getFormattedDueDate(context: Context): String {
        return DateUtils.getRelativeTimeSpanString(
            dueDate,
            System.currentTimeMillis(),
            DateUtils.DAY_IN_MILLIS
        ).toString()
    }
}

那么,@Ignore在这里扮演什么角色?它可以在数据转换的中间步骤中发挥作用。ViewModel或Repository在组装TaskViewState时,可能需要先查询出TaskEntity,然后通过一系列计算或关联查询,将多个数据源的信息整合。我们可以创建一个中间的数据持有类,利用@Ignore来临时存放这些中间状态,但最终目标仍是生成纯净的ViewState。

// 可选的中间数据类,用于Repository内部处理复杂查询
data class TaskWithDetails(
    @Embedded // 嵌入TaskEntity的所有字段
    val task: TaskEntity,

    // 通过Relation关联查询出的子任务列表
    @Relation(
        parentColumn = "id",
        entityColumn = "parentTaskId"
    )
    val subTasks: List<SubTaskEntity>,

    // 通过Relation关联查询出的分配用户信息
    @Relation(
        parentColumn = "assigneeId",
        entityColumn = "userId"
    )
    val assignee: UserEntity?,

    // 临时计算字段,用@Ignore标记
    @Ignore
    val isOverdue: Boolean = System.currentTimeMillis() > task.dueDate,

    @Ignore
    val completedSubTaskCount: Int = subTasks.count { it.isCompleted }
) {
    // 将中间数据转换为最终的ViewState
    fun toViewState(): TaskViewState {
        return TaskViewState(
            id = task.id,
            title = task.title,
            description = task.description,
            dueDate = task.dueDate,
            priority = task.priority,
            isOverdue = this.isOverdue, // 使用计算好的值
            priorityColorResId = when (task.priority) {
                1 -> R.color.priority_low
                2 -> R.color.priority_medium
                3 -> R.color.priority_high
                else -> R.color.priority_default
            },
            assigneeName = assignee?.displayName,
            subTaskSummary = "${completedSubTaskCount}/${subTasks.size}完成"
        )
    }
}

// 在Dao中定义返回TaskWithDetails的查询
@Dao
interface TaskDao {
    @Transaction // 因为涉及多个实体,建议使用事务注解
    @Query("SELECT * FROM tasks WHERE id = :taskId")
    suspend fun getTaskWithDetails(taskId: Long): TaskWithDetails?
}

这种模式清晰地将数据流划分为几个阶段:

  1. 持久化层:TaskEntity、SubTaskEntity、UserEntity,职责单一,只关心如何存储。
  2. 数据聚合层:TaskWithDetails,利用Room的@Embedded和@Relation进行关联查询,并用@Ignore承载临时计算属性,为转换做准备。
  3. 表现层:TaskViewState,包含所有UI需要的数据和格式,完全独立于数据库结构。

2.2 在ViewModel中协调数据流

ViewModel作为连接Repository(数据)和View(UI)的桥梁,其职责是调用Repository获取数据,并将其转换为ViewState。使用LiveData或StateFlow来驱动UI更新。

class TaskDetailViewModel @Inject constructor(
    private val taskRepository: TaskRepository
) : ViewModel() {

    private val _taskViewState = MutableStateFlow<TaskViewState?>(null)
    val taskViewState: StateFlow<TaskViewState?> = _taskViewState.asStateFlow()

    fun loadTask(taskId: Long) {
        viewModelScope.launch {
            // Repository返回TaskWithDetails(内部使用了@Ignore字段进行计算)
            val taskWithDetails = taskRepository.getTaskWithDetails(taskId)
            // 转换为纯净的ViewState
            taskWithDetails?.let {
                _taskViewState.value = it.toViewState()
            }
        }
    }

    // 处理UI事件,例如切换任务完成状态
    fun toggleTaskCompletion() {
        val currentState = _taskViewState.value ?: return
        viewModelScope.launch {
            // 调用Repository执行更新操作,这里操作的是领域模型或Entity
            taskRepository.markTaskCompleted(currentState.id, !currentState.isCompleted)
            // 重新加载数据
            loadTask(currentState.id)
        }
    }
}

3. 高级技巧:@Ignore与多构造函数模式应对复杂业务

@Ignore注解在构造函数上的应用,为Entity提供了多种初始化路径,这能优雅地解决一些特定的业务难题。

3.1 处理默认值与部分更新

在更新数据库记录时,我们有时只想更新部分字段。Room的@Update方法默认会使用Entity对象的所有非@Ignore字段去更新数据库中同名的所有列。如果我们从UI表单得到一个只包含部分更改的模型,直接用它来更新会导致其他字段被覆盖为默认值(如0、null或空字符串)。

解决方案是结合@Ignore构造函数和partialUpdate标志。

@Entity(tableName = "user_profiles")
data class UserProfileEntity(
    @PrimaryKey
    val userId: String,
    val displayName: String,
    val avatarUrl: String?,
    val bio: String,
    val location: String,
    val website: String,
    val lastUpdated: Long
) {
    // 全字段构造函数,用于从数据库加载或创建新对象
    constructor(
        userId: String,
        displayName: String,
        avatarUrl: String?,
        bio: String,
        location: String,
        website: String,
        lastUpdated: Long
    ) : this(userId, displayName, avatarUrl, bio, location, website, lastUpdated)

    // @Ignore构造函数:用于部分更新,未被提供的字段将保持原值
    @Ignore
    constructor(
        userId: String,
        displayName: String? = null,
        avatarUrl: String? = null,
        bio: String? = null
    ) : this(
        userId = userId,
        displayName = displayName ?: "", // 需要处理null,因为Entity字段通常非空
        avatarUrl = avatarUrl,
        bio = bio ?: "",
        location = "", // 赋予默认值,但通过一个标记让Update忽略它
        website = "",
        lastUpdated = 0L
    ) {
        // 我们可以添加一个自定义标记,但Room不直接支持。
        // 更常见的做法是在Dao层使用@Query进行自定义更新。
    }
}

// 在Dao中,我们为部分更新编写自定义SQL
@Dao
interface UserProfileDao {
    @Update
    suspend fun updateFullProfile(profile: UserProfileEntity)

    // 自定义更新,只更新非空的字段
    @Query("""
        UPDATE user_profiles 
        SET 
            displayName = COALESCE(:displayName, displayName),
            avatarUrl = COALESCE(:avatarUrl, avatarUrl),
            bio = COALESCE(:bio, bio),
            lastUpdated = :updateTime
        WHERE userId = :userId
    """)
    suspend fun updateProfilePartial(
        userId: String,
        displayName: String?,
        avatarUrl: String?,
        bio: String?,
        updateTime: Long
    )
}

在实际使用中,我们可以创建一个用于部分更新的数据类(如ProfileUpdateRequest),然后在Repository中将其转换为Dao需要的参数。@Ignore构造函数在这里的价值在于,它允许我们为一个Entity定义多种业务含义的创建方式,即使某些方式并不直接用于Room的数据绑定。

3.2 实现“软删除”与状态衍生字段

很多应用需要“软删除”功能,即不真正从数据库删除记录,而是用一个isDeleted字段标记。同时,我们可能有一些状态是衍生自其他字段的,比如订单的status可能由paymentStatus、shippingStatus等组合决定。

@Entity(tableName = "orders")
data class OrderEntity(
    @PrimaryKey
    val orderId: String,
    val userId: String,
    val totalAmount: Int,
    val paymentStatus: Int, // 0=未支付, 1=已支付, 2=退款中...
    val shippingStatus: Int, // 0=未发货, 1=已发货, 2=已签收...
    val createdAt: Long,
    @ColumnInfo(defaultValue = "0")
    val isDeleted: Boolean // 软删除标记
) {
    // 衍生状态:不存储,但业务逻辑中频繁使用
    @Ignore
    val derivedStatus: OrderStatus
        get() = calculateStatus()

    private fun calculateStatus(): OrderStatus {
        if (isDeleted) return OrderStatus.CANCELLED
        return when {
            paymentStatus == 0 -> OrderStatus.PENDING_PAYMENT
            shippingStatus == 0 -> OrderStatus.PAID_NOT_SHIPPED
            shippingStatus == 1 -> OrderStatus.SHIPPED
            shippingStatus == 2 -> OrderStatus.DELIVERED
            else -> OrderStatus.UNKNOWN
        }
    }

    // 提供一个便捷的“软删除”构造方式
    @Ignore
    constructor(orderId: String, markAsDeleted: Boolean) : this(
        orderId = orderId,
        userId = "", // 其他字段需要默认值,但不会被真正使用
        totalAmount = 0,
        paymentStatus = 0,
        shippingStatus = 0,
        createdAt = 0L,
        isDeleted = markAsDeleted
    ) {
        // 这个构造器可能用于创建一个仅包含ID和删除标记的“伪”对象,
        // 用于传递给Dao的@Delete方法(如果@Delete支持软删除逻辑的话)。
        // 更常见的做法是在Dao中用@Query实现软删除。
    }
}

// 在Dao中实现软删除
@Dao
interface OrderDao {
    @Query("UPDATE orders SET isDeleted = 1 WHERE orderId = :orderId")
    suspend fun softDelete(orderId: String)

    @Query("SELECT * FROM orders WHERE isDeleted = 0")
    suspend fun getActiveOrders(): List<OrderEntity>
}

这里,derivedStatus是一个典型的@Ignore属性,它通过get()方法实时计算,为业务逻辑提供了便利。而标记为@Ignore的构造函数则展示了一种思路:创建具有特定语义的Entity实例,尽管这个实例的大部分字段都是无意义的默认值,但其核心字段(如orderId和isDeleted)足以驱动特定的数据库操作。不过,对于软删除,更清晰的做法还是在Dao中直接使用@Query。

4. 架构演进:从混合Entity到清晰的分层模型

随着项目复杂度提升,对@Ignore的运用也应从“字段级”的修补,演进到“架构级”的设计。最终目标是让Entity只做一件事:定义数据库表结构。

4.1 定义纯净的Entity

理想的Entity应该像数据库表定义一样清晰、稳定。

// 纯净的、仅包含持久化字段的Entity
@Entity(tableName = "posts", indices = [Index(value = ["author_id", "created_at"])])
data class PostEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val title: String,
    val content: String,
    @ColumnInfo(name = "author_id")
    val authorId: Long,
    @ColumnInfo(name = "created_at")
    val createdAt: Long,
    @ColumnInfo(name = "updated_at")
    val updatedAt: Long,
    @ColumnInfo(name = "like_count")
    val likeCount: Int = 0,
    @ColumnInfo(name = "is_published")
    val isPublished: Boolean = false
)

4.2 创建独立的领域模型

领域模型包含业务规则和逻辑,它不关心数据如何存储。

// 领域模型
data class Post(
    val id: Long,
    val title: String,
    val content: String,
    val author: User, // 关联对象,而非ID
    val createdAt: Instant,
    val updatedAt: Instant,
    val stats: PostStats,
    val status: PostStatus
) {
    // 业务逻辑
    fun canBeEditedBy(user: User): Boolean {
        return user.id == author.id || user.role == UserRole.ADMIN
    }

    fun excerpt(maxLength: Int): String {
        return if (content.length <= maxLength) content else content.take(maxLength) + "..."
    }
}

// 值对象
data class PostStats(val likeCount: Int, val viewCount: Int, val commentCount: Int)
enum class PostStatus { DRAFT, PUBLISHED, ARCHIVED }

4.3 使用Mapper或转换函数进行映射

在Repository或数据层,实现Entity与领域模型之间的双向转换。这里@Ignore不再出现在Entity中,因为Entity已经足够简单。

// Mapper类(可以使用手动编写,或借助MapStruct等库)
class PostMapper @Inject constructor(
    private val userRepository: UserRepository
) {
    suspend fun toDomain(entity: PostEntity): Post {
        val author = userRepository.getUser(entity.authorId) // 获取关联对象
        return Post(
            id = entity.id,
            title = entity.title,
            content = entity.content,
            author = author,
            createdAt = Instant.ofEpochMilli(entity.createdAt),
            updatedAt = Instant.ofEpochMilli(entity.updatedAt),
            stats = PostStats(
                likeCount = entity.likeCount,
                viewCount = 0, // 可能需要从其他表查询
                commentCount = 0
            ),
            status = if (entity.isPublished) PostStatus.PUBLISHED else PostStatus.DRAFT
        )
    }

    fun toEntity(domain: Post): PostEntity {
        return PostEntity(
            id = domain.id,
            title = domain.title,
            content = domain.content,
            authorId = domain.author.id,
            createdAt = domain.createdAt.toEpochMilli(),
            updatedAt = domain.updatedAt.toEpochMilli(),
            likeCount = domain.stats.likeCount,
            isPublished = domain.status == PostStatus.PUBLISHED
        )
    }
}

4.4 处理复杂查询与关联数据

对于需要联合多个表数据的复杂查询,可以使用Room的@Embedded、@Relation或返回Data Class的@Query来创建专门的数据传输对象(DTO),而不是去污染Entity。

// 用于特定查询的DTO,不是Entity
data class PostDetailDto(
    @Embedded
    val post: PostEntity,
    @Relation(parentColumn = "author_id", entityColumn = "id")
    val author: UserEntity,
    @Relation(parentColumn = "id", entityColumn = "post_id")
    val comments: List<CommentEntity>,
    // 计算字段
    val commentCount: Int,
    val averageRating: Double?
) {
    // 可以在这里定义转换为领域模型或ViewState的方法
    suspend fun toPostDetail(authManager: AuthManager): PostDetail {
        return PostDetail(
            post = postMapper.toDomain(this.post),
            author = userMapper.toDomain(this.author),
            comments = this.comments.map { commentMapper.toDomain(it) },
            isLikedByCurrentUser = authManager.currentUser?.let { user ->
                // 查询当前用户是否点赞
                likeRepository.isPostLikedByUser(post.id, user.id)
            } ?: false
        )
    }
}

// 在Dao中定义返回DTO的查询
@Dao
interface PostDao {
    @Transaction
    @Query("""
        SELECT *, 
               (SELECT COUNT(*) FROM comments WHERE post_id = p.id) as commentCount,
               (SELECT AVG(rating) FROM post_ratings WHERE post_id = p.id) as averageRating
        FROM posts p
        WHERE p.id = :postId
    """)
    suspend fun getPostDetail(postId: Long): PostDetailDto?
}

在这个架构中,@Ignore的角色被弱化了,因为每个层级的模型都职责单一。Entity没有@Ignore字段,因为它只负责存储;DTO可以包含@Ignore的计算字段,因为它本身就是为特定查询场景服务的临时结构;领域模型和ViewState则完全独立于数据库。

回过头看,@Ignore注解就像Room框架提供的一个逃生舱口。在项目初期或简单场景中,你可以用它快速地为Entity添加一些临时属性,让代码跑起来。但随着架构的演进,你应该有意识地将这些被@Ignore标记的“临时工”重构到更合适的层级中去——要么是独立的领域模型,要么是专门的DTO,要么是ViewState。这个过程,正是代码从“能工作”到“好设计”的进化之路。

【电脑技巧】CPU正常运行时间过长,怎么解决? 目录 问题描述 解决方案 1、问题描述 不少同志都遇到过这样的问题:按【Ctrl + alt + delete】打开任务管理器,切到“性能”选项卡下,发现自己电脑的CPU正常运行时间竟然为好几十天!很多人担心这会不会影响电脑的寿命?于是很想知道这是怎么回事,以及如何解决这个问题。 我在此解答一下,这是由于你的电脑开启了“快速启动”这一选项的缘故,只要把这一选项关闭,问题就解决了。 2、解决方案 怎样关闭“快速启动”呢?按照如下的步骤来即可。 顺便声明一下,笔者的电脑配置: 使用电脑:联想Yoga 14 阅读详情

相关推荐

SAP FB03凭证修改实战:巧用BAPI FI_DOCUMENT_CHANGE更新抬头与行项目

本文详细介绍了SAP FB03凭证修改的实战技巧,重点解析了BAPI FI_DOCUMENT_CHANGE的使用方法。通过该接口,用户可以在不冲销原凭证的情况下直接更新抬头文本(BKTXT)和行项目文本(SGTXT),确保凭证编号连续且符合审计要求。文章包含核心机制解析、完整实现步骤、错误处理方案以及高级应用场景,帮助开发者高效完成SAP财务凭证修改。

weixin_29251567的博客 425

Android 使用kotlin data class定义 Room实体类在使用@Ignore注解时的注意事项

一、本文核心要点: 使用kotlin的data class定义Room实体数据类时,不要在构造函数中定义忽略属性。 二、个人使用定义data class的方式如下: @Entity(tableName = "category_qw") data class Next( val sort: Int, @PrimaryKey val typeId: Int, val typeLevel: Int, val typeName: String, val typ

yann02的博客 3106

H无穷控制学习笔记——加权灵敏度H无穷控制问题

一、加权灵敏度H无穷问题 系统结构如上图所示。被控对象P的状态空间方程为(Ap,Bp,Cp,DpA_p,B_p,C_p,D_pAp​,Bp​,Cp​,Dp​),加权函数W1的状态空间方程为(A1,B1,C1,D1A_1,B_1,C_1,D_1A1​,B1​,C1​,D1​),加权函数W2的状态空间方程为(A2,B2,C2,D2A_2,B_2,C_2,D_2A2​,B2​,C2​,D2​),加权函数W3的状态空间方程为(A3,B3,C3,D3A_3,B_3,C_3,D_3A3​,B3​,C3​,D3​)。

一饮丶一啄的博客 4350

Room简单实操

默认情况下,Room 将类名称用作数据库表名称。如果希望表具有不同的名称,请设置@Entity注解的tableName属性。Room 默认使用字段名称作为数据库中的列名称。如果您希望列具有不同的名称,请将注解添加到该字段并设置name属性。SQLite 中的表和列名称不区分大小写。在 Room 数据库中,主键通常定义为长整型(long)是因为长整型提供了更大的范围,可以容纳更多的唯一标识符。唯一标识符范围。

Silicon_Valley 1860

FOC实战笔记:RC吸收电路的设计考量与参数调优

本文深入探讨了FOC驱动器中RC吸收电路的设计与调优。针对MOS管开关过程中的电压尖峰问题,详细阐述了RC电路的钳位与能量吸收原理,并提供了从参数计算、实验调试到布局布线的完整实战指南,旨在帮助工程师有效提升系统可靠性与稳定性。

weixin_29219539的博客 275

银河麒麟桌面版V10搭建Qt4.8.6开发环境

银河麒麟桌面版V10搭建Qt4.8.6开发环境

qq_34932364的博客 2400

Android Jetpack(8):Room

Room介绍 Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。 处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备之后重新连接到网络后,用户发起的所有内容更改都会同步到服务器。 由于 Room 负责为...

qq_36699930的博客 659

毕设加分项!手把手教你完成基于YOLOv8与PyQt5的多类别目标检测系统——让你的毕设从“完成”到“出色”

从KITTI数据集处理,到YOLOv8模型训练,再到PyQt5界面开发,整个流程其实没有想象中那么难。只要你跟着步骤一步步来,不仅能顺利完成毕设,还能掌握目标检测和GUI开发的核心技能——这些技能在未来的求职、读研中都非常实用。如果过程中遇到问题,别担心!可以去YOLOv8的官方文档、PyQt5论坛找答案,也可以和同学、老师交流。记住,完成毕设的过程也是你技术成长的过程,坚持下去,你会为自己的成果感到自豪。

m0_71589036的博客 179

Android笔记(二十一):Room组件实现Android应用的持久化处理

介绍Android JetPack开发套件的Room组件实现数据库的处理。

userhu2012的博客 2327

SAP_MM模块非限制库存转冻结库存

项目非限制 → 冻结事务码移动类型343影响库存不可用,不参与MRP常见用途质量问题封存、盘点冻结、待退货品通过本文,我们了解了 SAP 的[SAP_MM模块非限制库存转冻结库存]的基本概念和应用方法。希望这些信息能帮助您在实际工作中更好地使用 SAP 系统,提高工作效率和管理水平。更多SAP文章请点击更多SAP文章更新,大家一起学习进步!!!文章中如有错误。敬请指正!搜索公众号:SAP资料文库微信号:SPRO_PP时间:2025-10-14。

我小时候很黑的博客 586

2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)

2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)

王老师青少年编程 990

Android Jetpack应用指南学习笔记4之Room的简单使用

1.Android采用Sqlite作为数据库存储。Sqlite代码写起来繁琐且容易出错,所以开源社区里逐渐出现了各种ORM(Object Relational Mapping)库。这些开源ORM库都是为了方便Sqlite的使用,包括数据库的创建,升级,增删改查等。常见的ORM有ORMLite,GreenDAO等。Google也意识到了推出自家ORM的必要性,于是有了Room。 2.Room 包含 3 个主要组件: 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。使.

u012556114的博客 2621

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Android Room 使用详解

Android Room 使用详解

Snow_Ice_Yang的博客 4356

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Room报错

Entities and POJOs must have a usable public constructor. You can either provide `room.schemaLocation` annotation processor argument OR set exportSchema to false. Room cannot verify the data integrity. Looks like you've changed schema but forgot to update

qq_50675668的博客 537

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Room的创建方法

代码后序还要自行添加get和set方法来访问私有变量,不然room无法获取数据。@Ignore用于让room忽略这个构造方法,这样子room在使用的时候就会使用全参构造,不用则会报错。@Ignore也可以用于注释成员变量,如此做的话room不会创建这个属性。

NorthM1的博客 65

解决Kotlin Room报错:多构造函数选择与@Ignore注解的正确使用

本文深入解析了Kotlin Room开发中常见的“多构造函数选择”编译警告,并提供了系统解决方案。文章指出,当实体类存在多个构造函数(如使用@JvmOverloads)时,Room会默认选择无参构造函数,可能导致数据填充错误。核心解决方案是移除@JvmOverloads,并正确使用@Ignore注解来标记非主构造函数,确保Room仅使用一个明确的构造函数进行反序列化。同时,文章强调了保持Kotlin与Room版本兼容的重要性,并分享了多种场景下的@Ignore进阶用法与最佳实践,帮助开发者构建稳健的数据层。

ee345的博客 400

3-6 Room数据库使用详解

Room数据库使用详解 一、基础使用 依赖库 implementation 'androidx.room:room-runtime:2.2.5' implementation 'androidx.room:room-common:2.2.5' implementation 'androidx.room:room-ktx:2.2.5' kapt "androidx.room:room-compiler:2.2.5"//class DbUser和接口UserDao关....

opooooi的博客 908

Android架构之Room组件(一)

Room基本概念 Android采用Sqlite作为数据库存储。由于Sqlite代码写起来烦琐且容易出错,Android官方推出了一个ORM库(Room),Room就是为了方便Sqlite的使用而出现的,包括数据库的创建、升级、增/删/改/查等操作。Room相对来说就是对Sqlite上提供了一层封装。 Room介绍 Room包含三个主要组件 数据库: 包含数据库的持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入者 使用@Database注解的类应满足以下条件 是继承RoomDatabase

qq_43404873的博客 2496

Android Jetpack组件(五)Room

Android使用SQLite作为数据库存储数据,但是SQLite使用繁琐且容易出错,有许多开源的数据如GreenDAO、ORMLite等,这些都是为了方便SQLite的使用而出现的,Google也意识到了这个问题,在Jetpack组件中推出了Room,Room在SQLite上提供了一层封装,可以流畅的访问数据库。

八归少年 2613

Android Room简要指南---摘自官方指南

一: 使用 Room 将数据保存到本地数据库 注:本文摘自安卓开发文档,仅供学习使用,链接如下:安卓开发文档ROOM Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。 处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备之后重新连接到网络后,用户发起的所有内容更改都会同步到服务器。 *注意:要在应用中使用 Room,请在应用的 build.

小小菜下士的博客 2036

Android Jetpack(五)Room

Room

Fighting_初心的博客 587

探索Android架构组件Room

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34220623的博客 336

Jetpack中Room的使用

Room Room主要分三个部分 database、dao和实体类entity Entity entity实体类定义时需要用到@Entity(tableName = "student")注解,其中参数为表名 主键定义需要用到@PrimaryKey(autoGenerate = true)注解,参数决定是否自增长 每个属性(也就是表的字段)都需要加@ColumnInfo(name = "name",typeAffinity = ColumnInfo.TEXT)注解 ,name是表中的字段名、 typ

IT_zhazhanan的博客 535

mysql中IGNORE使用

一、批量插入数据忽略失败的数据行 数据表的字段,ID为主键 当我们执行一下插入语句时: 执行结果: 我们看到报错了一个主键冲突的错,1确实主键冲突了,其实4主键没有冲突,这是我们想将不冲突的数据依然插入到数据库,可以执行以下的语句 执行结果: 我们可以看到,插入成功了,并影响了一行数据. 我们看下数据表: id等于4的这条数据已经插入进来了. 总结:加上ignore 关键字,可以将其中...

weixin_40980639的博客 1万+

[译] Architecture Components 之 Room Persistence Library

【目录】 1. Architecture Components 之 Guide to App Architecture 2. Architecture Components 之 Adding Components to your Project 3. Architecture Components 之 Handling Lifecycles 4. Architecture Components 之...

weixin_34296641的博客 412

Android架构组件Room的使用

Android架构组件Room的使用 Room其实就是一个orm,抽象了SQLite的使用,但是它作为Android的亲儿子orm,并且原生支持LiveData和Rxjava嵌套使用,学习一下还是不错的。 Room有3个主要组件 Database :数据库 Entity : 代表数据库一个表结构 Dao : 包含访问数据库的方法 简单使用 添加Google Maven仓库 allproject...

weixin_34235135的博客 396
上一篇: 在麒麟系统上利用VSCODE和C#实现高效图片处理:SkiaSharp实战指南
下一篇: 2024年最新LaTeX环境搭建:TexLive+TexStudio保姆级教程(附清华镜像加速)
五行擒拿术
博客等级 码龄26年 54粉丝 1361原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值