使用OneToOne关系时,typeorm框架的保存处理方式有一些要点:
什么情况下会插入外键列,即实体对象之间的属性如何设置才会插入相关外键列,如user.post=post还是post.user=user 什么情况下需要先保存主表对象,才能保存相关联的从表对象 什么情况下可以直接保存从表对象,框架会先级联保存主表对象 能否通过保存主表对象,级联保存相关联的从表对象 这里Post实体对应主表,User实体对应从表,外键在从表中,从表引用主表 1.默认选项 实体:
import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { User} from './User' @Entity() export class Post { @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>User,user=>user.post) user:User } import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { Post } from './Post' @Entity() export class User{ @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>Post,post=>post.user) @JoinColumn() post:Post }save方法进行保存:
import { createConnection ,Repository} from 'typeorm' import { User } from './entity/User' import { Post } from './entity/Post' createConnection({ name:'test', type:'mysql', database:'test', host:'localhost', port:3306, username:'root', password:'123456', charset:'UTF8', entities:[User,Post], synchronize:true, dropSchema:true, logging:'all', logger:'simple-console' }).then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) user.post = post await postRepository.save(post) await userRepository.save(user) })在上面的保存方式中,如果没有先保存主表对象post,而直接保存从表对象user,会报错:
Cannot add or update a child row: a foreign key constraint fails (`test`.`user`, CONSTRAINT `fk_41caccae10590801e045be12f56` FOREIGN KEY (`postId`) REFERENCES `post` (`id`))这是因为在插入user对象时,框架检测到post属性,则自动插入postId列,而数据库插入postId时,启动外键检查,发现指定id的post数据不存在,报出数据库错误。这说明在框架中将user对象的post属性与postId关联起来是一种默认行为,也就是框架处理关系的基本行为,不必进行多余的选项配置,如果post属性为null,则postId也为null
save方法保存的另一种情况:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) post.user = user await postRepository.save(post) await userRepository.save(user) })保存结果:
mysql> select * from user,post; +----+------+--------+----+----------+ | id | name | postId | id | name | +----+------+--------+----+----------+ | 1 | 李四 | NULL | 1 | 滴滴滴滴 | +----+------+--------+----+----------+ 1 row in set (0.00 sec)说明,只设置post.user=user时,在保存user时不能获取其关联的post对象,进而postId为null,这是语言的限制,所以必须要设置从表对象user的post属性才能保存外键
insert方法:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) user.post = post await postRepository.insert(post) await userRepository.insert(user) })保存结果:
mysql> select * from user,post; +----+------+--------+----+----------+ | id | name | postId | id | name | +----+------+--------+----+----------+ | 1 | 李四 | 1 | 1 | 滴滴滴滴 | +----+------+--------+----+----------+ 1 row in set (0.00 sec)说明默认选项下,insert与save保存效果相同,insert中依然会将user的post属性与postId列关联起来,这个默认行为不会改变
2.主表级联保存
实体:
import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { User} from './User' @Entity() export class Post { @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>User,user=>user.post,{ cascadeInsert:true }) user:User } import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { Post } from './Post' @Entity() export class User{ @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>Post,post=>post.user) @JoinColumn() post:Post }save保存:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) post.user = user await postRepository.save(post) })保存结果:
mysql> select * from user,post; +----+------+--------+----+----------+ | id | name | postId | id | name | +----+------+--------+----+----------+ | 1 | 李四 | 1 | 1 | 滴滴滴滴 | +----+------+--------+----+----------+ 1 row in set (0.00 sec)说明:主表中设置cascadeInsert:true时,只保存post对象,可以关联保存user对象
此时的实际保存顺序类似于表面看到的,即先保存post,然后获取其user属性,设置postId,保存user
也就是说此时cascadeInsert的内部默认行为为:获取其关系属性user,然后将先前保存的post对象的id设置为user的postId,然后保存user对象
save方法的另一种情况:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) user.post = post await postRepository.save(post) console.log('执行完毕'+new Date()) })保存结果:
mysql> select * from user; Empty set (0.00 sec) mysql> select * from post; +----+----------+ | id | name | +----+----------+ | 1 | 滴滴滴滴 | +----+----------+ 1 row in set (0.00 sec)说明,主表级联保存,必须设置主表对象的user属性,才能进行级联
insert方法:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) post.user = user await postRepository.insert(post) })保存结果:
mysql> select * from user; Empty set (0.00 sec) mysql> select * from post; +----+----------+ | id | name | +----+----------+ | 1 | 滴滴滴滴 | +----+----------+ 1 row in set (0.00 sec)说明insert方法触发不了cascadeInsert的内部默认行为,即无法发现post的user属性,并设置其postId,再插入它,insert方法无法做到这些
但是在前面发现,insert保存user时,可以根据其post属性设置postId,这个行为是可以触发的
3.从表级联保存 实体:
import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { User} from './User' @Entity() export class Post { @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>User,user=>user.post) user:User } import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { Post } from './Post' @Entity() export class User{ @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>Post,post=>post.user,{ cascadeInsert:true }) @JoinColumn() post:Post }save方法保存:
then(async connection=>{ let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) user.post = post await userRepository.save(user) console.log('执行完毕'+new Date()) })报错:
Cannot add or update a child row: a foreign key constraint fails (`test`.`user`, CONSTRAINT `fk_41caccae10590801e045be12f56` FOREIGN KEY (`postId`) REFERENCES `post` (`id`))老错误,还是先保存user时,进行外键检查出错
这里我们期待的是,typeorm框架能先保存user对象的post属性,然后再保存user对象(包含postId),但是实际上框架还是直接保存user,且设置了postId,出错
说明typeorm框架可以保存主表对象时级联保存从表对象,不可以在保存从表对象时级联保存主表对象
typeorm中的级联保存都是顺序保存,先保存顶层对象,再查找属性进行保存,不能先找到需要保存的主表对象属性,然后保存它
4.主表多次级联保存
实体:
import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { User} from './User' @Entity() export class Post { @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>User,user=>user.post,{ cascadeInsert:true }) user:User } import { Entity,PrimaryColumn,Column,OneToOne,JoinColumn} from 'typeorm' import { Post } from './Post' @Entity() export class User{ @PrimaryColumn('int') id:number; @Column('varchar') name:string @OneToOne(type=>Post,post=>post.user) @JoinColumn() post:Post }
save方法多次保存:
then(async connection=>{ console.log('开始执行') let userRepository:Repository<User> = connection.getRepository(User) let postRepository:Repository<Post> = connection.getRepository(Post) let user:User = userRepository.create({id:1,name:'李四'}) let post:Post = postRepository.create({id:1,name:'滴滴滴滴'}) post.user = user await postRepository.save(post) console.log('保存完成') post.user = userRepository.create({id:2,name:'王五'}) await postRepository.save(post) console.log(await userRepository.find()) console.log(await postRepository.find()) console.log('执行完毕'+new Date()) })
保存结果:
mysql> select * from user; +----+------+--------+ | id | name | postId | +----+------+--------+ | 1 | 李四 | 1 | | 2 | 王五 | 1 | +----+------+--------+ 2 rows in set (0.00 sec) mysql> select * from post; +----+----------+ | id | name | +----+----------+ | 1 | 滴滴滴滴 | +----+----------+ 1 row in set (0.00 sec)
可以说是失败了,因为这不是OneToOne,这是MnayToOne,这是因为OneToOne关系的外键应该为unique'列,但是框架没有自动给外键列添加unique约束,需要自己添加
这里还有一个问题,因为OneToOne装饰器只有一个,在关系的两边都使用了它,但是由于关系本身不是对称的,所以有一些属性只会在一边起作用
如这里看到的cascadeInsert只会在主表中起作用,而onDelete属性只会在从表中起作用
关于保存,这里主要测试了以下几个问题:
1.默认情况下save、insert方法,如何触发框架设置外键列的
结果:他们都可以触发框架将从表对象user的post属性关联到外键列postId,可见这是一个基本行为
2.save、insert方法,对cascadeInsert的触发
结果:只有save方法可以触发cascadeInsert,这在文档中也是说明了的
3.save级联保存时的方向
结果:只能在保存主表对象时级联保存作为其属性的从表对象,不能通过保存从表对象级联保存主表对象,这个在别的orm框架中不知道如何设定
