8

In a Spring Boot project I have a JPA entity, like this:

@Entity
public class Account {
}

and then I have the repository to query the database:

public interface AccountRepository extends JpaRepository<Account, UUID> {

}

In both the app and tests it's easy to get the repository by doing:

@Autowired
private AccountRepository accountRepository;

How can I get hold of the repository in a method in the Account class? I tried @Autowired but it didn't work.

For those arguing about design, my question is not about design. I have written this code in Ruby and Rails as well as Clojure with HugSQL and it was self contained and concise: when creating a new record I also generate a compact unique alphanumeric id. In Ruby on Rails I released it as a library: https://github.com/pupeno/random_unique_id

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622
  • 3
    Are you sure there is not a better solution than accessing a repository from an entity? – davioooh Sep 07 '17 at 09:30
  • @davioooh: yes, I'm pretty sure. Entities before being saved need to generate some data and that data depends on existing records. – Pablo Fernandez Sep 07 '17 at 09:38
  • 2
    ok... I think that entities should remain unaware about repositories. I personally prefer to implement extra logic in Service layer... but if are sure, go for this... – davioooh Sep 07 '17 at 09:44
  • I'd still like to know how to do this. – Pablo Fernandez Sep 07 '17 at 09:46
  • 1
    Possible duplicate of [Autowired not working in a Class @Entity](https://stackoverflow.com/questions/28365154/autowired-not-working-in-a-class-entity) – davioooh Sep 07 '17 at 09:48
  • 1
    @Pablo usually you put the extra logic in service layer of your application. But if you really need this i think you you could try `@EntityListeners` with`@PrePersist` http://www.concretepage.com/java/jpa/jpa-entitylisteners-example-with-callbacks-prepersist-postpersist-postload-preupdate-postupdate-preremove-postremove – varren Sep 07 '17 at 09:48
  • @varren how does the entity listener access the repository? – Pablo Fernandez Sep 07 '17 at 09:53
  • Why not deletage creating entities to repository instead? – M. Prokhorov Sep 07 '17 at 10:04
  • Injection is not magical wand, for example not for for `new` creation, and such idea is bad design – Jacek Cz Sep 07 '17 at 10:17
  • For those that prefer to talk about the design of my system, I created another question with those details and I welcome any ideas on how to do it the right way: https://stackoverflow.com/questions/46094487/whats-the-proper-way-of-generating-a-unique-id-using-spring-boot-jpa – Pablo Fernandez Sep 07 '17 at 10:52
  • @Pablo heh, yeah i tried to implement this aaand it was kinda hacky. Actually i thought that `EntityListeners` has access to the application context. anyway you could try and play with this if you want to. https://github.com/varren/EntityListenerAutowiredRepositoryHack – varren Sep 07 '17 at 11:43

5 Answers5

3

I had same question and found this Github repository.

Here are some parts of code:

...
import de.ck35.example.ddd.jpa.SpringEntityListener;
...

@Entity
@Table(name="bookshelf")
@EntityListeners(SpringEntityListener.class)
public class BookshelfEntity implements Bookshelf {

    ...


    private String category;

    @Autowired transient BookRepository bookRepository;
    @Autowired transient BookshelfSpaceRepository bookshelfSpaceRepository;

Where de.ck35.example.ddd.jpa.SpringEntityListener purpose is described in Javadoc:

/**
 * Entity listener which allows dependency injection inside entities.
 * The listener can be registered via {@link EntityListeners} annotation.
 * 
 * Dependency injection annotations like {@link Autowired} are supported.
 * 
 * @author Christian Kaspari
 * @since 1.0.0
 */
public class SpringEntityListener {

    ...

    @PostLoad
    @PostPersist
    public void inject(Object object) {
        AutowireCapableBeanFactory beanFactory = get().getBeanFactory();
        if(beanFactory == null) {
            LOG.warn("Bean Factory not set! Depdendencies will not be injected into: '{}'", object);
            return;
        }
        LOG.debug("Injecting dependencies into entity: '{}'.", object);
        beanFactory.autowireBean(object);
    }

There is also configuration which enables Repositories definitions nested in classes (in this case entities):

@Configuration
@EnableJpaRepositories(basePackages="de.ck35.example.ddd.jpa", considerNestedRepositories=true)
@ComponentScan("de.ck35.example.ddd.jpa")
public class JpaConfiguration {

Thanks to Christian Kaspari who is author of this repository.

David
  • 1,920
  • 25
  • 31
2

You can access the Spring Application Context from static method and use this static method to load your repository bean in your @Entity class instead of autowiring it.

You need to create the following classes (found here):

ApplicationContextProvider

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    public ApplicationContext getApplicationContext() {
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        context = ctx;
    }
}

SpringConfiguration

@Configuration
public class SpringConfiguration {

    @Bean
    public static ApplicationContextProvider contextProvider() {
        return new ApplicationContextProvider();
    }

}

And then

@Entity
public class Account {
    //your code

    public void doAccountRepositoryStuff() {
        AccountRepository accountRepository = (AccountRepository) SpringConfiguration.contextProvider().getApplicationContext().getBean("accountRepository");
        // Do your own stuff with accountRepository here...
    }
}
pleft
  • 7,567
  • 2
  • 21
  • 45
0

I'm guessing you are trying to implement something like the Active Record pattern in Hibernate. It's quite unusual but you could annotate your entity with @Configurable so you can get internally @Autowired to work (Also ensure entity package is within your @ComponentScan reach)

dimitrisli
  • 20,895
  • 12
  • 59
  • 63
0

You have to use @GenericGenerator and @GeneratedValue see an example here. Please notice that in the IdentifierGenerator implemntation you can access the repository.

If this is for non-id field, then the work around will be to make a new entity GeneralSequenceNumber and its ID is the required generated ID, then make one to one mapping between GeneralSequenceNumber and your Account entity (as mentioned here)

@Entity
public class GeneralSequenceNumber {
 @Id
 @GenericGenerator(name = "sequence_dep_id", strategy = "com.xyz.ids.DepartmentIdGenerator")
 @GeneratedValue(generator = "sequence_dep_id")  
 @Column(name="Department_Id")
 private String deptId;
}

@Entity 
public class Account {
  @Id ..
  private Long id;

  @OneToOne(...)
  private GeneralSequnceNumber myVal;
} 
Hasson
  • 1,894
  • 1
  • 21
  • 25
0

I saw the method below in a project because there were no Service layer at that project. To be honest, I recommend adding Service layer instead of these solutions but if you really want/have to use it, the solution is below.

Create Repository Interface

public interface BookRepository extends JpaRepository<Book, String> {
}

Create a Component

@Component
public class Registry {

private static BookRepository bookRepository;

static BookRepository bookRepo() {
    return bookRepository;
}

}

Achieve the repo by using static function in the Component

import static com.x.Registry.bookRepo;
@Entity
class Book {
    public static List<Book> all() {
        return bookRepo().findAll();
    }
}