Как изменить тип Объекта в JPA?

Если данные поступают в файл, могут быть реализованы таким образом:

  1. Считать файл как CSV;
  2. Добавить столбец индекса с помощью «monotonically_increasing_id»
  3. Сначала выберите столбец, а все остальные столбцы как массив.

На Scala можно реализовать таким образом:

val df = spark.read.option("header", "false").csv("non-csv.txt")
val remainingColumns = df.columns.tail
df.withColumn("id", monotonically_increasing_id).
  select(
    col("id"),
    col(df.columns(0)),
    array(remainingColumns.head, remainingColumns.tail: _*)
  ).show(false)

Вывод:

+---+---+--------------------+
|id |_c0|array(_c1, _c2, _c3)|
+---+---+--------------------+
|0  |abc|[ x1,  x2,  x3]     |
|1  |def|[ x1,  x3,  x4]     |
|2  |ghi|[ x7,  x10,  x11]   |
+---+---+--------------------+
10
задан Robert Campbell 17 April 2009 в 18:21
поделиться

5 ответов

Итак, я наконец нашел рабочее решение:

Отключить EntityManager для обновления DTYPE . Это главным образом потому, что Query.executeUpdate () должен выполняться внутри транзакции. Вы можете попробовать запустить его в рамках существующей транзакции, но это, вероятно, связано с тем же контекстом сохраняемости сущности, которую вы изменяете. Это означает, что после обновления DTYPE вы должны найти способ evict () сущности. Самый простой способ - вызвать entityManager.clear () , но это приводит к всевозможным побочным эффектам (об этом читайте в спецификации JPA). Лучшее решение - получить базовый делегат (в моем случае Hibernate Session ) и вызовите Session.evict (пользователь) . Это, вероятно, будет работать на простых доменных графах, но мои были очень сложными. Мне никогда не удавалось заставить @Cascade (CascadeType.EVICT) корректно работать с моими существующими аннотациями JPA, например @OneToOne (cascade = CascadeType.ALL) . Я также попытался вручную передать свой домен графа Session и заставить каждую родительскую сущность выселить своих детей. Это также не сработало по неизвестным причинам.

Я остался в ситуации, когда будет работать только entityManager.clear () , но я не мог принять побочные эффекты. Затем я попытался создать отдельный блок персистентности специально для конвертации сущностей. Я подумал, что могу локализовать операцию clear () только для того ПК, который отвечает за преобразования. Я установил новый компьютер, новый соответствующий EntityManagerFactory , новый менеджер транзакций для него, и вручную добавление этого менеджера транзакций в репозиторий для ручной упаковки executeUpdate () в транзакцию, соответствующую соответствующему ПК. Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

В этот момент я выбросил все и создал этот класс:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class JdbcUserConversionRepository implements UserConversionRepository {

@Resource
private UserService userService;

private JdbcTemplate jdbcTemplate;

@Override
@SuppressWarnings("unchecked")
public User convertUserType(final User user, final Class targetClass) {

        // Update the DTYPE
        jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });

        // Before we try to load our converted User back into the Persistence
        // Context, we need to remove them from the PC so the EntityManager
        // doesn't try to load the cached one in the PC. Keep in mind that all
        // of the child Entities of this User will remain in the PC. This would
        // normally cause a problem when the PC is flushed, throwing a detached
        // entity exception. In this specific case, we return a new User
        // reference which replaces the old one. This means if we just evict the
        // User, then remove all references to it, the PC will not be able to
        // drill down into the children and try to persist them.
        userService.evictUser(user);

        // Reload the converted User into the Persistence Context
        return userService.getUserById(user.getId());
    }

    public void setDataSource(final DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
}

Есть две важные части этого метода, которые, я считаю, заставляют его работать:

  1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) для него новый менеджер транзакций и ручная вставка этого менеджера транзакций в репозиторий для ручного переноса функции executeUpdate () в транзакцию, соответствующую соответствующему ПК. Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

    В этот момент я выбросил все и создал этот класс:

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public class JdbcUserConversionRepository implements UserConversionRepository {
    
    @Resource
    private UserService userService;
    
    private JdbcTemplate jdbcTemplate;
    
    @Override
    @SuppressWarnings("unchecked")
    public User convertUserType(final User user, final Class targetClass) {
    
            // Update the DTYPE
            jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
    
            // Before we try to load our converted User back into the Persistence
            // Context, we need to remove them from the PC so the EntityManager
            // doesn't try to load the cached one in the PC. Keep in mind that all
            // of the child Entities of this User will remain in the PC. This would
            // normally cause a problem when the PC is flushed, throwing a detached
            // entity exception. In this specific case, we return a new User
            // reference which replaces the old one. This means if we just evict the
            // User, then remove all references to it, the PC will not be able to
            // drill down into the children and try to persist them.
            userService.evictUser(user);
    
            // Reload the converted User into the Persistence Context
            return userService.getUserById(user.getId());
        }
    
        public void setDataSource(final DataSource dataSource) {
            this.jdbcTemplate = new JdbcTemplate(dataSource);
        }
    }
    

    Есть две важные части этого метода, которые, я считаю, заставляют его работать:

    1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) для него новый менеджер транзакций и ручная вставка этого менеджера транзакций в репозиторий для ручного переноса функции executeUpdate () в транзакцию, соответствующую соответствующему ПК. Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

      В этот момент я выбросил все и создал этот класс:

      @Transactional(propagation = Propagation.NOT_SUPPORTED)
      public class JdbcUserConversionRepository implements UserConversionRepository {
      
      @Resource
      private UserService userService;
      
      private JdbcTemplate jdbcTemplate;
      
      @Override
      @SuppressWarnings("unchecked")
      public User convertUserType(final User user, final Class targetClass) {
      
              // Update the DTYPE
              jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
      
              // Before we try to load our converted User back into the Persistence
              // Context, we need to remove them from the PC so the EntityManager
              // doesn't try to load the cached one in the PC. Keep in mind that all
              // of the child Entities of this User will remain in the PC. This would
              // normally cause a problem when the PC is flushed, throwing a detached
              // entity exception. In this specific case, we return a new User
              // reference which replaces the old one. This means if we just evict the
              // User, then remove all references to it, the PC will not be able to
              // drill down into the children and try to persist them.
              userService.evictUser(user);
      
              // Reload the converted User into the Persistence Context
              return userService.getUserById(user.getId());
          }
      
          public void setDataSource(final DataSource dataSource) {
              this.jdbcTemplate = new JdbcTemplate(dataSource);
          }
      }
      

      Есть две важные части этого метода, которые, я считаю, заставляют его работать:

      1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) и ручное внедрение этого диспетчера транзакций в репозиторий для ручного переноса функции executeUpdate () в транзакцию, соответствующую соответствующему ПК. Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

        В этот момент я выбросил все и создал этот класс:

        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public class JdbcUserConversionRepository implements UserConversionRepository {
        
        @Resource
        private UserService userService;
        
        private JdbcTemplate jdbcTemplate;
        
        @Override
        @SuppressWarnings("unchecked")
        public User convertUserType(final User user, final Class targetClass) {
        
                // Update the DTYPE
                jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
        
                // Before we try to load our converted User back into the Persistence
                // Context, we need to remove them from the PC so the EntityManager
                // doesn't try to load the cached one in the PC. Keep in mind that all
                // of the child Entities of this User will remain in the PC. This would
                // normally cause a problem when the PC is flushed, throwing a detached
                // entity exception. In this specific case, we return a new User
                // reference which replaces the old one. This means if we just evict the
                // User, then remove all references to it, the PC will not be able to
                // drill down into the children and try to persist them.
                userService.evictUser(user);
        
                // Reload the converted User into the Persistence Context
                return userService.getUserById(user.getId());
            }
        
            public void setDataSource(final DataSource dataSource) {
                this.jdbcTemplate = new JdbcTemplate(dataSource);
            }
        }
        

        Есть две важные части этого метода, которые, я считаю, заставляют его работать:

        1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) и ручное внедрение этого диспетчера транзакций в репозиторий для ручного переноса функции executeUpdate () в транзакцию, соответствующую соответствующему ПК. Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

          В этот момент я выбросил все и создал этот класс:

          @Transactional(propagation = Propagation.NOT_SUPPORTED)
          public class JdbcUserConversionRepository implements UserConversionRepository {
          
          @Resource
          private UserService userService;
          
          private JdbcTemplate jdbcTemplate;
          
          @Override
          @SuppressWarnings("unchecked")
          public User convertUserType(final User user, final Class targetClass) {
          
                  // Update the DTYPE
                  jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
          
                  // Before we try to load our converted User back into the Persistence
                  // Context, we need to remove them from the PC so the EntityManager
                  // doesn't try to load the cached one in the PC. Keep in mind that all
                  // of the child Entities of this User will remain in the PC. This would
                  // normally cause a problem when the PC is flushed, throwing a detached
                  // entity exception. In this specific case, we return a new User
                  // reference which replaces the old one. This means if we just evict the
                  // User, then remove all references to it, the PC will not be able to
                  // drill down into the children and try to persist them.
                  userService.evictUser(user);
          
                  // Reload the converted User into the Persistence Context
                  return userService.getUserById(user.getId());
              }
          
              public void setDataSource(final DataSource dataSource) {
                  this.jdbcTemplate = new JdbcTemplate(dataSource);
              }
          }
          

          Есть две важные части этого метода, которые, я считаю, заставляют его работать:

          1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

            В этот момент я выбросил все и создал этот класс:

            @Transactional(propagation = Propagation.NOT_SUPPORTED)
            public class JdbcUserConversionRepository implements UserConversionRepository {
            
            @Resource
            private UserService userService;
            
            private JdbcTemplate jdbcTemplate;
            
            @Override
            @SuppressWarnings("unchecked")
            public User convertUserType(final User user, final Class targetClass) {
            
                    // Update the DTYPE
                    jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
            
                    // Before we try to load our converted User back into the Persistence
                    // Context, we need to remove them from the PC so the EntityManager
                    // doesn't try to load the cached one in the PC. Keep in mind that all
                    // of the child Entities of this User will remain in the PC. This would
                    // normally cause a problem when the PC is flushed, throwing a detached
                    // entity exception. In this specific case, we return a new User
                    // reference which replaces the old one. This means if we just evict the
                    // User, then remove all references to it, the PC will not be able to
                    // drill down into the children and try to persist them.
                    userService.evictUser(user);
            
                    // Reload the converted User into the Persistence Context
                    return userService.getUserById(user.getId());
                }
            
                public void setDataSource(final DataSource dataSource) {
                    this.jdbcTemplate = new JdbcTemplate(dataSource);
                }
            }
            

            Есть две важные части этого метода, которые, я считаю, заставляют его работать:

            1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) Здесь я должен сказать, что я недостаточно знаю о транзакциях, управляемых контейнером Spring / JPA, потому что это оказалось кошмаром, пытаясь заставить локальную / ручную транзакцию для executeUpdate () хорошо играть с Управляемая контейнером транзакция извлекается из уровня Service.

              В этот момент я выбросил все и создал этот класс:

              @Transactional(propagation = Propagation.NOT_SUPPORTED)
              public class JdbcUserConversionRepository implements UserConversionRepository {
              
              @Resource
              private UserService userService;
              
              private JdbcTemplate jdbcTemplate;
              
              @Override
              @SuppressWarnings("unchecked")
              public User convertUserType(final User user, final Class targetClass) {
              
                      // Update the DTYPE
                      jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });
              
                      // Before we try to load our converted User back into the Persistence
                      // Context, we need to remove them from the PC so the EntityManager
                      // doesn't try to load the cached one in the PC. Keep in mind that all
                      // of the child Entities of this User will remain in the PC. This would
                      // normally cause a problem when the PC is flushed, throwing a detached
                      // entity exception. In this specific case, we return a new User
                      // reference which replaces the old one. This means if we just evict the
                      // User, then remove all references to it, the PC will not be able to
                      // drill down into the children and try to persist them.
                      userService.evictUser(user);
              
                      // Reload the converted User into the Persistence Context
                      return userService.getUserById(user.getId());
                  }
              
                  public void setDataSource(final DataSource dataSource) {
                      this.jdbcTemplate = new JdbcTemplate(dataSource);
                  }
              }
              

              Есть две важные части этого метода, которые, я считаю, заставляют его работать:

              1. Я пометил его как @Transactional (пропаганда = Propagation.NOT_SUPPORTED) который должен приостановить управляемую контейнером транзакцию, поступающую с уровня Service, и позволить преобразованию происходить вне ПК.
              2. Перед попыткой перезагрузить преобразованную сущность обратно в ПК, я высылаю старую копию, хранящуюся в настоящее время на ПК. с userService.evictUser (пользователь); . Код для этого - просто получить экземпляр Session и вызвать evict (user) . См. Комментарии в коде для получения более подробной информации, но в основном, если мы этого не сделаем, любые вызовы getUser попытаются вернуть кэшированный объект еще на ПК, за исключением того, что он выдаст ошибку о типе быть другим.

              Хотя мои первоначальные тесты прошли успешно, у этого решения все еще могут быть некоторые проблемы. Я буду держать это в курсе, так как они раскрыты.

3
ответ дан 4 December 2019 в 03:39
поделиться

Вы действительно должны представлять уровень подписки в типе User? Почему бы вам не добавить тип подписки в вашей системе?

Отношение тогда будет выражаться как: у пользователя есть одна подписка. Это также может быть смоделировано как отношение «один ко многим», если вы хотите сохранить историю подписок.

Затем, когда вы захотите изменить уровень подписки пользователя вместо создания нового пользователя, вы создадите нового Подписка и назначение ее пользователю.

// When you user signs up
User.setSubscription(new FreeSubscription());

// When he upgrades to a paying account
User.setSubscription(new PayingSubscription());

Действительно ли TrialUser отличается от PayingUser? Возможно нет. Вам лучше будет использовать агрегацию, а не наследование.

Данные подписки могут храниться в отдельной таблице (сопоставленной как сущность) или в таблице пользователей (сопоставленной как компонент).

2
ответ дан 4 December 2019 в 03:39
поделиться

Проблема больше связана с Java, чем с чем-либо еще. Вы не можете изменить тип экземпляра во время выполнения (во время выполнения), поэтому hibernate не предусматривает такого рода сценариев (в отличие, например, от rails).

Если вы хотите исключить пользователя из сеанса, вы будете должны выселить связанные лица. У вас есть несколько вариантов:

  • Сопоставить объект с помощью evict = cascade (см. Session # evict javadoc)
  • Вручную удалить все связанные объекты (это может быть громоздким)
  • Очистить сеанс, таким образом исключая все сущности (конечно, вы потеряете локальный кеш сеанса)

У меня была похожая проблема в grails, и мое решение аналогично решению Григория . Я копирую все поля экземпляра, включая связанные сущности, затем удаляю старую сущность и пишу новую. Если у вас не так много отношений, это может быть легко, но если ваша модель данных сложна, вам лучше использовать описанное вами решение sql.

Вот источник, если вам интересно:

def convertToPacient = {
    withPerson(params.id) {person ->
      def pacient = new Pacient()
      pacient.properties = person.properties
      pacient.id = null
      pacient.processNumber = params.processNumber
      def ap = new Appointment(params)
      pacient.addToAppointments(ap);

      Person.withTransaction {tx ->
        if (pacient.validate() && !pacient.hasErrors()) {

          //to avoid the "Found two representations of same collection" error
          //pacient.attachments = new HashSet(person.attachments);
          //pacient.memberships = new HashSet(person.memberships);
          def groups = person?.memberships?.collect {m -> Group.get(m.group.id)}
          def attachs = []
          person.attachments.each {a ->
            def att = new Attachment()
            att.properties = a.properties
            attachs << att
          }

          //need an in in order to add the person to a group
          person.delete(flush: true)
          pacient.save(flush: true)
          groups.each {g -> pacient.addToGroup(g)};
          attachs.each {a -> pacient.addToAttachments(a)}
          //pacient.attachments.each {att -> att.id = null; att.version = null; att.person = pacient};

          if (!pacient.save()) {
            tx.setRollbackOnly()
            return
          }
        }
      }
1
ответ дан 4 December 2019 в 03:39
поделиться

Когда вы говорите «преобразование», вы, вероятно, искажаете проблему. По моему мнению, вы действительно пытаетесь создать экземпляр одного класса на основе экземпляра другого класса, например:

public PayingUser(TrialUser theUser) {
...
}

Затем вы можете удалить старого пробного пользователя и сохранить нового платящего пользователя.

-1
ответ дан 4 December 2019 в 03:39
поделиться

Проблема больше связана с Java чем что-либо еще. Вы не можете изменить тип времени выполнения экземпляра (при время выполнения), поэтому спящий режим не предусмотреть такие сценарии (в отличие от рельсов, например.)

Я столкнулся с этим сообщением, потому что у меня (или думал, что у меня есть) аналогичная проблема. Я пришел к выводу, что проблема заключается не в механике смены типов в технологии, а в том, что развитие типов в целом совсем не просто и непросто. Проблема в том, что типы довольно часто имеют разные атрибуты. Если вы не относитесь к разным типам только для различий в поведении, неясно, что делать с дополнительной информацией. По этой причине имеет смысл переносить типы на основе построения новых объектов.

Проблема не «связана с Java». это связано с теорией типов в целом и более конкретно здесь с объектно-реляционным отображением. Если ваш язык поддерживает набор текста, я Мне нравится слышать, как вы можете автоматически изменять тип объекта во время выполнения и волшебным образом делать что-то разумное с оставшейся информацией. Пока мы распространяем FUD: будьте осторожны, от кого вы берете совет: Rails - это фреймворк, Ruby - это язык.

0
ответ дан 4 December 2019 в 03:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: