前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用晋升加薪,讲解DDD领域模型中的对象设计 —— 聚合、实体、值对象

用晋升加薪,讲解DDD领域模型中的对象设计 —— 聚合、实体、值对象

作者头像
小傅哥
发布于 2023-09-06 03:41:12
发布于 2023-09-06 03:41:12
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

作者:小傅哥 博客:https://bugstack.cn

❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞

此外本文也通过关于雇员薪酬调整的案例,渗透讲解 DDD 模型中的聚合对象、实体对象和值对象在领域模型中的实践。

同时也通过简单干净实践的方式教会读者,使用 SpringBoot 配置 MyBatis 并完成对插入、批量插入、修改、查询以及注解事务和编程事务的使用,通过扩展插件开发对置顶字段进行加解处理。

本文涉及的工程:

  • xfg-dev-tech-mybatis:https://gitcode.net/KnowledgePlanet/road-map/xfg-dev-tech-mybatis
  • 导入测试库表:road-map.sql - 测试工程下

一、案例背景

说一千道一万,给小卡拉米写的教程,得简单还好看!

为了更好的把 MyBatis 常用的各项功能体现的清晰明了,小傅哥这里设定了公司雇员和对应薪酬关系的一个开发场景。

  • 首先,雇员员工和对应的薪资待遇,是一个1v1的关系。
  • 之后,薪资表与调薪表,是一个1vn的关系。每次晋升、普调,都会有一条对应的调薪记录。
  • 最后,有了这样3个表,我们就可以很好的完成,员工的插入、批量插入,和事务操作调薪。

二、领域模型

🌶 模型定义:https://bugstack.cn/md/road-map/ddd.html - 你可以先参考小傅哥的 DDD 篇,这样可以更好的理解模型概念和设计原则。

DDD 领域驱动设计的中心,主要在于领域模型的设计,以领域所需驱动功能实现和数据建模。一个领域服务下面会有多个领域模型,每个领域模型都是一个充血结构。一个领域模型 = 一个充血结构

  • model 模型对象;
    • aggreate:聚合对象,实体对象、值对象的协同组织,就是聚合对象。
    • entity:实体对象,大多数情况下,实体对象(Entity)与数据库持久化对象(PO)是1v1的关系,但也有为了封装一些属性信息,会出现1vn的关系。
    • valobj:值对象,通过对象属性值来识别的对象 By 《实现领域驱动设计》
  • repository 仓储服务;从数据库等数据源中获取数据,传递的对象可以是聚合对象、实体对象,返回的结果可以是;实体对象、值对象。因为仓储服务是由基础层(infrastructure) 引用领域层(domain),是一种依赖倒置的结构,但它可以天然的隔离PO数据库持久化对象被引用。
  • service 服务设计;这里要注意,不要以为定义了聚合对象,就把超越1个对象以外的逻辑,都封装到聚合中,这会让你的代码后期越来越难维护。聚合更应该注重的是和本对象相关的单一简单封装场景,而把一些重核心业务方到 service 里实现。此外;如果你的设计模式应用不佳,那么无论是领域驱动设计、测试驱动设计还是换了三层和四层架构,你的工程质量依然会非常差。

此场景的业务用于对指定的用户进行晋升加薪调幅,但因为加薪会需要操作3个表,包括;雇员表 - 修改个人Title、薪资表 - 修改薪酬、调薪记录表 - 每一次加薪都写一条记录。

1. model

1.1 值对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public enum EmployeePostVO {

    T1("T-1", "初级工程师"),
    T2("T-2", "初级工程师"),
    T3("T-3", "中级工程师"),
    T4("T-4", "中级工程师"),
    T5("T-5", "高级工程师"),
    T6("T-6", "高级工程师"),
    T7("T-7", "架构师"),
    T8("T-8", "架构师");

    private final String code;
    private final String desc;

  // 省略部分

}
  • 当一个实体对象中的一个值,是有多个范围时候,则需要定义出值对象。由于此类的值对象更贴近于当前的场景业务,所以一般不会被定义为共用的枚举。如此此类值范围,都会被定义为值对象。
1.2 实体对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeEntity {

    /** 雇员级别 */
    private EmployeePostVO employeeLevel;
    /** 雇员岗位Title */
    private EmployeePostVO employeeTitle;

}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeSalaryAdjustEntity {

    /** 总额调薪 */
    private BigDecimal adjustTotalAmount;
    /** 基础调薪 */
    private BigDecimal adjustBaseAmount;
    /** 绩效调薪 */
    private BigDecimal adjustMeritAmount;

}
  • 实体对象是对数据库对象的抽象,大多数时候是 1:1 的关系结构,在一些复杂的模型场景中会是1:n的结构。
1.3 聚合对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AdjustSalaryApplyOrderAggregate {

    /** 雇员编号 */
    private String employeeNumber;
    /** 调薪单号 */
    private String orderId;
    /** 雇员实体 */
    private EmployeeEntity employeeEntity;
    /** 雇员实体 */
    private EmployeeSalaryAdjustEntity employeeSalaryAdjustEntity;

}
  • 聚合对象是对实体对象和值对象的封装,代表着一类业务的聚合。通常是作为 service 服务层中入参出现。

2. repository

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ISalaryAdjustRepository {

    String adjustSalary(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate);

}
  • 仓储在 DDD 中的设计,是一种依赖倒置关系,由 domain 定义接口,之后由引入 domain 包的基层层 infrastructure 实现功能。
  • 此外,因为是依赖倒置,所以天然的隔离了 PO 数据库持久化对象,不会被对外使用。这个设计是非常巧妙的。当我们从结构上定义了原则,就不会有人乱引用对象了。

3. service

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ISalaryAdjustApplyService {

    String execSalaryAdjust(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate);

}
  • 当前场景简单,所以不需要额外的设计模式使用。但如果是复杂场景,必须考虑设计模式,否则代码都写到 SalaryAdjustApplyService 实现类里,那么将非常难维护。
  • 不要只是把聚合对象当充血模型,你的充血结构是整个 domain 下的每一个领域包,也就是让这里的状态与行为看做为一整个结构。

📢 综上,有了这样的模型结构设计定义,相信你也可以很好的拆分自己的业务对象并完成领域功能实现了。

三、配置文件

  • 工程中关于 MyBatis 的使用,在 xfg-dev-tech-app 下进行统一配置。因为所有配置信息都放到一起,比较方便管理,也利于线上上线后,提取配置文件。

四、功能实现

接下来我们介绍一些关于 MyBatis 的使用功能,但你可以带着 DDD 的思想来看这些内容实现时所在的位置,这会让你不只是学习 MyBatis 也能学会一些 DDD 的设计。

1. 插入&批量插入

源码:cn.bugstack.xfg.dev.tech.infrastructure.dao.IEmployeeDAO

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Mapper
public interface IEmployeeDAO {

    void insert(EmployeePO employee);

    void insertList(List<EmployeePO> list);

    void update(EmployeePO employeePO);

    EmployeePO queryEmployeeByEmployNumber(String employNumber);

}

xml:employee_mapper.xml

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<insert id="insert" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.po.EmployeePO">
    INSERT INTO employee(employee_number, employee_name, employee_level, employee_title, create_time, update_time)
    VALUES(#{employeeNumber}, #{employeeName}, #{employeeLevel}, #{employeeTitle}, now(), now())
</insert>

<insert id="insertList" parameterType="java.util.List">
    INSERT INTO employee(employee_number, employee_name, employee_level, employee_title, create_time, update_time)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.employeeNumber}, #{item.employeeName}, #{item.employeeLevel}, #{item.employeeTitle}, now(), now())
    </foreach>
</insert>
  • 使用配置文件的方式比较好维护,当然如果也可以尝试使用 MyBatis 提供的注解方式,完成数据的操作。

2. 事务&注解编程

Spring 提供的事务分为注解事务和编程事务,编程事务可以更细粒度的控制。

Spring Boot 事务管理的级别可以通过 @Transactional 注解的 isolation 属性进行配置。常见的事务隔离级别有以下几种:

  1. DEFAULT:使用底层数据库的默认隔离级别。MySQL 默认为 REPEATABLE READ,Oracle 默认为 READ COMMITTED
  2. READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、不可重复读和幻读问题。
  3. READ_COMMITTED:允许读取已经提交的数据变更,可以避免脏读问题,但可能会出现不可重复读和幻读问题。
  4. REPEATABLE_READ:保证同一事务中多次读取同一数据时,结果始终一致,可以避免脏读和不可重复读问题,但可能会出现幻读问题。
  5. SERIALIZABLE:最高的隔离级别,可以避免脏读、不可重复读和幻读问题,但会影响并发性能。

在 Spring Boot 中,默认的事务隔离级别为 DEFAULT。如果没有特殊需求,建议使用默认隔离级别。

SpringBoot 事务的传播行为可以通过 @Transactional 注解的 propagation 属性进行配置。常用的传播行为有以下几种:

  1. Propagation.REQUIRED:默认的传播行为,如果当前存在事务,则加入该事务,否则新建一个事务;
  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务的方式执行;
  3. Propagation.MANDATORY:如果当前存在事务,则加入该事务,否则抛出异常;
  4. Propagation.REQUIRES_NEW:无论当前是否存在事务,都会新建一个事务,如果当前存在事务,则将当前事务挂起;
  5. Propagation.NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起;
  6. Propagation.NEVER:以非事务的方式执行操作,如果当前存在事务,则抛出异常;
  7. Propagation.NESTED:如果当前存在事务,则在该事务的嵌套事务中执行,否则新建一个事务。嵌套事务是独立于外部事务的,但是如果外部事务回滚,则嵌套事务也会回滚。

除了传播行为,@Transactional 注解还可以配置其他属性,例如隔离级别、超时时间、只读等。

2.1 注解事务

源码:cn.bugstack.xfg.dev.tech.infrastructure.repository.SalaryAdjustRepository

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Transactional(rollbackFor = Exception.class, timeout = 350, propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public String adjustSalary(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate) {
    String employeeNumber = adjustSalaryApplyOrderAggregate.getEmployeeNumber();
    String orderId = adjustSalaryApplyOrderAggregate.getOrderId();
    EmployeeEntity employeeEntity = adjustSalaryApplyOrderAggregate.getEmployeeEntity();
    EmployeeSalaryAdjustEntity employeeSalaryAdjustEntity = adjustSalaryApplyOrderAggregate.getEmployeeSalaryAdjustEntity();
    EmployeePO employeePO = EmployeePO.builder()
            .employeeNumber(employeeNumber)
            .employeeLevel(employeeEntity.getEmployeeLevel().getCode())
            .employeeTitle(employeeEntity.getEmployeeTitle().getDesc()).build();
    // 更新岗位
    employeeDAO.update(employeePO);
    EmployeeSalaryPO employeeSalaryPO = EmployeeSalaryPO.builder()
            .employeeNumber(employeeNumber)
            .salaryTotalAmount(employeeSalaryAdjustEntity.getAdjustTotalAmount())
            .salaryMeritAmount(employeeSalaryAdjustEntity.getAdjustMeritAmount())
            .salaryBaseAmount(employeeSalaryAdjustEntity.getAdjustBaseAmount())
            .build();
    // 更新薪酬
    employeeSalaryDAO.update(employeeSalaryPO);
    EmployeeSalaryAdjustPO employeeSalaryAdjustPO = EmployeeSalaryAdjustPO.builder()
            .employeeNumber(employeeNumber)
            .adjustOrderId(orderId)
            .adjustTotalAmount(employeeSalaryAdjustEntity.getAdjustTotalAmount())
            .adjustBaseAmount(employeeSalaryAdjustEntity.getAdjustMeritAmount())
            .adjustMeritAmount(employeeSalaryAdjustEntity.getAdjustBaseAmount())
            .build();
    // 写入流水
    employeeSalaryAdjustDAO.insert(employeeSalaryAdjustPO);
    return orderId;
}
  • 这个事务所做的内容,就是前面小傅哥提到的调整薪资的处理。它的具体操作就是放到仓储层实现。
  • 注意事务注解的配置。
2.2 编程事务
2.2.1 事务模板
  • 使用编程事务,需要在这里创建出一个事务模板,当然你不创建也可以使用。只不过这样统一的配置会更加方便。
2.2.2 事务使用
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private TransactionTemplate transactionTemplate;
@Override
public void insertEmployeeInfo(EmployeeInfoEntity employeeInfoEntity) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                EmployeePO employeePO = EmployeePO.builder()
                        .employeeNumber(employeeInfoEntity.getEmployeeNumber())
                        .employeeName(employeeInfoEntity.getEmployeeName())
                        .employeeLevel(employeeInfoEntity.getEmployeeLevel())
                        .employeeTitle(employeeInfoEntity.getEmployeeTitle())
                        .build();
                employeeDAO.insert(employeePO);
                EmployeeSalaryPO employeeSalaryPO = EmployeeSalaryPO.builder()
                        .employeeNumber(employeeInfoEntity.getEmployeeNumber())
                        .salaryTotalAmount(employeeInfoEntity.getSalaryTotalAmount())
                        .salaryMeritAmount(employeeInfoEntity.getSalaryMeritAmount())
                        .salaryBaseAmount(employeeInfoEntity.getSalaryBaseAmount())
                        .build();
                employeeSalaryDAO.insert(employeeSalaryPO);
            } catch (Exception e) {
                status.setRollbackOnly();
                e.printStackTrace();
            }
        }
    });
}
  • 之后就可以手动处理事务了,因为手动的处理可以更细节的控制,也可以根据返回的结果,手动回滚。而不非得异常回滚。

3. 插件&数据加密

使用 MyBatis 时,也会经常会用到插件开发。尤其是做一些数据的加解密、路由、日志等,都可以基于插件实现。

那么这里小傅哥就带着你实现一个对指定字段加解密的处理,比如雇员的姓名、薪资、级别是可以隐藏的,避免被有心之人盗取。

源码:cn.bugstack.xfg.dev.tech.plugin.FieldEncryptionAndDecryptionMybatisPlugin

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class FieldEncryptionAndDecryptionMybatisPlugin implements Interceptor {

    /**
     * 密钥,必须是16位
     */
    private static final String KEY = "1898794876567654";
    /**
     * 偏移量,必须是16位
     */
    private static final String IV = "1233214566547891";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        String sqlId = mappedStatement.getId();

        if (parameter != null && (sqlId.contains("insert") || sqlId.contains("update")) ) {
            String columnName = "employeeName";
            if (parameter instanceof Map) {
                List<Object> parameterList = (List<Object>) ((Map<?, ?>) parameter).get("list");
                for (Object obj : parameterList) {
                    if (hasField(obj, columnName)) {
                        String fieldValue = BeanUtils.getProperty(obj, columnName);
                        String encryptedValue = encrypt(fieldValue);
                        BeanUtils.setProperty(obj, columnName, encryptedValue);
                    }
                }
            } else {
                if (hasField(parameter, columnName)) {
                    String fieldValue = BeanUtils.getProperty(parameter, columnName);
                    String encryptedValue = encrypt(fieldValue);
                    BeanUtils.setProperty(parameter, columnName, encryptedValue);
                }
            }
        }

        Object result = invocation.proceed();

        if (result != null && sqlId.contains("query")) {
            // 查询操作,解密
            String columnName = "employeeName";
            if (result instanceof List) {
                List<Object> resultList = (List<Object>) result;
                for (Object obj : resultList) {
                    if (!hasField(obj, columnName)) continue;
                    String fieldValue = BeanUtils.getProperty(obj, columnName);
                    if (StringUtils.isBlank(fieldValue)) continue;
                    String decryptedValue = decrypt(fieldValue);
                    BeanUtils.setProperty(obj, columnName, decryptedValue);
                }
            }
        }

        return result;
    }

    public String encrypt(String content) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = KEY.getBytes();
        SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] encrypted = cipher.doFinal(content.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    /**
     * AES解密
     *
     * @param content 密文
     * @return 明文
     * @throws Exception 异常
     */
    public String decrypt(String content) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = KEY.getBytes();
        SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] encrypted = Base64.getDecoder().decode(content);
        byte[] original = cipher.doFinal(encrypted);
        return new String(original);
    }

    public boolean hasField(Object obj, String fieldName) {
        Class<?> clazz = obj.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                return true;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        return false;
    }

}
  • 首先通过注解配置,拦截指定范围内的信息 Intercepts 之后在 intercept 接口实现方法中,获取 MappedStatement 这个 MyBatis的映射核心类。
  • 有了 AES 的加解密,就可以对指定的字段 employeeName 对插入数据库的字段进行加密,同时还可以在读取的时候解密。

五、测试验证

1. 调薪

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void test_execSalaryAdjust() {
    AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate = AdjustSalaryApplyOrderAggregate.builder()
            .employeeNumber("10000001")
            .orderId("100908977676001")
            .employeeEntity(EmployeeEntity.builder().employeeLevel(EmployeePostVO.T3).employeeTitle(EmployeePostVO.T3).build())
            .employeeSalaryAdjustEntity(EmployeeSalaryAdjustEntity.builder()
                    .adjustTotalAmount(new BigDecimal(100))
                    .adjustBaseAmount(new BigDecimal(80))
                    .adjustMeritAmount(new BigDecimal(20)).build())
            .build();
    String orderId = salaryAdjustApplyService.execSalaryAdjust(adjustSalaryApplyOrderAggregate);
    log.info("调薪测试 req: {} res: {}", JSON.toJSONString(adjustSalaryApplyOrderAggregate), orderId);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
23-07-15.13:23:11.514 [main            ] INFO  HikariDataSource       - HikariPool-1 - Start completed.
23-07-15.13:23:11.910 [main            ] INFO  ISalaryAdjustApplyServiceTest - 调薪测试 req: {"employeeEntity":{"employeeLevel":"T3","employeeTitle":"T3"},"employeeNumber":"10000001","employeeSalaryAdjustEntity":{"adjustBaseAmount":80,"adjustMeritAmount":20,"adjustTotalAmount":100},"orderId":"100908977676002"} res: 100908977676002

2. 查询

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void test_queryEmployInfo() {
    EmployeeInfoEntity employeeInfoEntity = employeeService.queryEmployInfo("10000001");
    log.info("测试结果:{}", JSON.toJSONString(employeeInfoEntity));
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
23-07-15.13:24:54.000 [main            ] INFO  HikariDataSource       - HikariPool-1 - Start completed.
23-07-15.13:24:54.490 [main            ] INFO  IEmployeeServiceTest   - 测试结果:{"employeeLevel":"T-3","employeeName":"小傅哥","employeeNumber":"10000001","employeeTitle":"中级工程师","salaryBaseAmount":5200.00,"salaryMeritAmount":5200.00,"salaryTotalAmount":5200.00}
  • 执行完调薪后,就可以来看下这个用户的薪资待遇是多少了。
  • 如果你真想学习到DDD架构,以及面试中能讲出些东西,那么一定加入小傅哥的星球,因为星球里有6个实战项目并还在增加!这些项目会帮助你非常好的提升架构思维与编程能力。☞ 下面扫码了解下。

- END -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 bugstack虫洞栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
热钱涌入,巨头入局,VR/AR行业离爆发还有多远?
  近日,在2021世界VR产业大会云峰会上发布的《虚拟现实产业发展白皮书(2021年)》(简称“报告”)显示,2021年1-9月,虚拟现实产业累计投融资金额已达到207.09亿元,投融资事件数量和融资金额均已超过以往历年全年的总额。
科技旋涡
2022/03/30
2730
热钱涌入,巨头入局,VR/AR行业离爆发还有多远?
4大趋势之外 清醒认知“元宇宙”
  在国家语言资源监测与研究中心发布的“2021年度十大网络用语”中,有一个词语跟“双减”“躺平”等热词一同登榜,成为年度最火的网络用语之一。在过去的一年里,尽管它频繁地出现在我们生活和工作中的很多场景,但实话实说,很少有人能说清楚它究竟是什么。这个火热的词语就是“元宇宙”,一个看上去就浩瀚无边的词语。
科技旋涡
2022/03/30
2780
4大趋势之外 清醒认知“元宇宙”
字节90亿,腾讯30亿,巨头争夺元宇宙入场券
‍从腾讯提出的“全真互联网”到如今巨头纷纷布局的“元宇宙”,VR产业已被业内视为率先登陆下一代互联网阵地的“船票”。
镁客网
2022/02/10
3360
字节90亿,腾讯30亿,巨头争夺元宇宙入场券
蹭热点?炒概念?争议之下,看大厂们如何讲中国元宇宙的故事
元宇宙中国市场争夺战或许已经打响?6月中旬开始,元宇宙再次成为科技圈的热门话题,继国外Meta、雅虎宣布在中国香港推出元宇宙试点后,短短一周时间,又有三家国内公司传出涉足元宇宙的消息。
数据猿
2022/09/04
4620
蹭热点?炒概念?争议之下,看大厂们如何讲中国元宇宙的故事
进军元宇宙社交!字节重金「买」下海归博士团队,创始人曾掌舵小米VR
---- 新智元报道   编辑:拉燕 好困 【新智元导读】为布局元宇宙,字节又买了一家公司! 这次,字节跳动再次祭出「招聘式收购」。 在天眼查上,一笔最新的投资浮出水面——收购北京波粒子科技有限公司。 其创始人兼CEO马杰思将出任「Pico社交中心负责人」,而他50余人的团队也被整体并入到了Pico社交中心。 字节的元宇宙,要跳动了吗? 博士归来,曾任小米VR负责人 说起马杰思,也算是既能开公司、又能搞研究的全才了。 本科就读于北京航天大学,硕士研究生就读于北大,博士毕业于美国乔治梅森大学。
新智元
2022/06/29
2750
进军元宇宙社交!字节重金「买」下海归博士团队,创始人曾掌舵小米VR
介入元宇宙社交?字节跳动回应“派对岛”和元宇宙无关!近半年加码布局元宇宙赛道
1月27日,有媒体报道,字节跳动将推出元宇宙社交APP“派对岛”,并称这是继百度 “希壤”后的第二款元宇宙社交产品。字节跳动旗下抖音相关负责人告诉中国证券报·中证金牛座记者,“派对岛”和元宇宙无关,是一个仍在小范围内测的社交产品,必须有邀请码才可以使用。
科技旋涡
2022/03/30
4840
介入元宇宙社交?字节跳动回应“派对岛”和元宇宙无关!近半年加码布局元宇宙赛道
大火的元宇宙,这把火能烧多久?
近日,资本市场的元宇宙概念突然崛起变得异常火爆,二级市场的相关个股纷纷开启暴涨模式。除此之外各家大厂也开始加码布局元宇宙赛道,相关收并购新闻活跃在各个媒体平台上,不论是硬件设备公司还是软件内容公司,开始的受到资本关注。
刘旷
2021/09/09
5500
高瓴、红杉重金入场 资本争夺的“元宇宙”是风口还是泡沫
  10月26日上午,Facebook发布了第3季度财报,其中,Facebook披露今年要为旗下元宇宙部门投入至少百亿美元,以进一步推动AR/VR软硬件和相关内容资源的开发。
科技旋涡
2022/03/30
2850
高瓴、红杉重金入场 资本争夺的“元宇宙”是风口还是泡沫
元宇宙很远,云XR很近
没错,这是电影《失控玩家》中出现的“绿洲”场景——玩家们通过VR设备在虚拟世界中自由探索、娱乐和生活。也是最近爆火的“元宇宙”概念描绘的美丽新世界。
曾响铃
2021/09/24
8420
“元宇宙”概念引发AR/VR新一轮投资潮,去年Q4融资总额达120亿,超过此前2年总和
明敏 发自 凹非寺 量子位 | 公众号 QbitAI 仅1个季度就吸引投资19亿美元 (折合人民币约120亿元),占全年总融资额的一半,AR/VR初创公司们最近真的是“赢麻了”。 据Crunchbase news统计,在刚刚过去的2021年第四季度,AR/VR初创公司的融资金额创下历史新高。 △图源:crunchbase news 这使得2021年成为VR/AR领域有史以来投资第二好的一年,全年融资达39亿美元(折合人民币约247亿元),仅次于2018年的44亿美元(折合人民币约278亿元)。 而从平均
量子位
2022/03/04
2770
8月VR圈大厂融资并购消息不断,元宇宙赛道如火如荼
(VRPinea 9月13日讯)立秋后的8月,天气依旧那么热。虽然天气很热,上班很累,但值得欣慰的是,下个月中秋和国庆假期很快就要来了。那么在这个闷热的8月,VR圈又发生了哪些大事呢?
VRPinea
2021/10/08
8430
字节跳动的Pico,能追上Meta的Oculus吗?
“错过了智能手机及其操作系统的设计,只能沦为谷歌和苹果的附庸,只有有了自己的硬件平台和系统,才能当家做主。”
Alter聊科技
2023/01/13
3840
2018年VR/AR十大关键词(八):TO VC-TO B/TO G
编者按:临近年底,VRPinea又开始了2018年VR/AR十大关键词的盘点。今年VRPinea选择的关键词为5G、赋能、Magic Leap、虚拟偶像、《头号玩家》、《Beat Saber》、VR一体机、TO VC-TO B/TO G、行业变动、Facebook。其中,本期关键词为:TO VC-TO B/TO G。
VRPinea
2019/01/02
1.1K0
2022年元宇宙十大发展趋势
“元宇宙”成功入选由国家语言资源监测与研究中心发布的“2021年度十大网络用语”,由此足以可见元宇宙在2021年的火爆程度。展望2022,元宇宙又会呈现哪些趋势呢?
晓吾
2022/01/21
5910
2022年元宇宙十大发展趋势
逐渐炎热的6月里,VR行业也在蒸腾而上
(VRPinea 7月2日讯)2020年的6月与往年有很大的不同,大概是因为高考受疫情影响延期至7月,亦或是各大高校在线毕业的措施使无数毕业生无法回校再度相聚,毕业季的氛围平淡许多。回归VR,因疫情影响,2020年上半年整体呈现疲态,好在6月苹果与索尼分别召开的WWDC 2020及PS5线上发布会在平静的湖面上投下了一块巨石,并激起了层层波浪。
VRPinea
2020/07/06
7270
逐渐炎热的6月里,VR行业也在蒸腾而上
安信国际科技行业深度分析:元宇宙的行业布局思考
  元宇宙(Metaverse)最早起源于科幻小说。1992年,科幻作家尼尔·斯蒂芬森在《雪崩》里 提及了“Metaverse(元宇宙)”和“Avatar(化身)”这两个概念——人们在Metaverse中 可以拥有自己的虚拟替身,而这个虚拟的世界就叫作“元宇宙”。在其后的接近30年间,元 宇宙的概念在《黑客帝国》《头号玩家》等影视作品及《模拟人生》、《我的世界》、《堡 垒之夜》等游戏中都有所呈现。
科技旋涡
2022/03/30
1.1K0
安信国际科技行业深度分析:元宇宙的行业布局思考
字节、Meta死磕元宇宙
Facebook在一年前高调地更名为Meta,All In 元宇宙。一年过去,虽然大多数普通用户依然不知“元宇宙”为何物也搞不清“元宇宙”跟VR/AR到底有什么关系,但Meta却凭借一己之力,让元宇宙从科幻词汇成为产业概念。在Meta的引领下众多科技企业投身“元宇宙”赛道,所有VR/AR/软件企业摇身一变成了元宇宙公司。
罗超频道
2022/12/14
4460
字节、Meta死磕元宇宙
问询函也压不住?元宇宙概念股再迎涨停潮
  11月5日,佳创视讯再次20%涨停,连录得3个涨停板,天舟文化也获得“20cm”涨幅,当虹科技上涨13.62%,蓝色光标上涨13.27%,易尚展示、凯撒文化均10%涨停,汤姆猫、会畅通讯、大富科技等均获得超过5%的涨幅。
科技旋涡
2022/03/30
3200
问询函也压不住?元宇宙概念股再迎涨停潮
赵晓光:Facebook是观察元宇宙行业最核心指标,苹果到MR或AR可能有三个阶段
“要站在比较高的高度看元宇宙,就是我们人类从物质时代进入到虚拟时代、精神时代,精神消费和满足实际上是未来人类主要的一个追求。”
科技旋涡
2022/03/30
2110
赵晓光:Facebook是观察元宇宙行业最核心指标,苹果到MR或AR可能有三个阶段
哪些因素促进了元宇宙的发展?
元宇宙在过去一年得到了极大的关注,许多大企业甚至是国家都纷纷开展元宇宙的布局。那么究竟是哪些因素促进了元宇宙的发展?这当中,新时代的消费变化、疫情带来的大变局、相关技术的成熟以及资本的驱动成为了最大的催化剂。
小将
2022/03/17
4250
哪些因素促进了元宇宙的发展?
推荐阅读
相关推荐
热钱涌入,巨头入局,VR/AR行业离爆发还有多远?
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 一、案例背景
  • 二、领域模型
    • 1. model
      • 1.1 值对象
      • 1.2 实体对象
      • 1.3 聚合对象
    • 2. repository
    • 3. service
  • 三、配置文件
  • 四、功能实现
    • 1. 插入&批量插入
    • 2. 事务&注解编程
      • 2.1 注解事务
      • 2.2 编程事务
    • 3. 插件&数据加密
  • 五、测试验证
    • 1. 调薪
    • 2. 查询
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档