2024.03.17 - [프로그래밍언어/JAVA] - 순수 JAVA JPA
순수 JAVA JPA
https://docs.spring.io/spring-framework/reference/data-access/orm/jpa.html JPA :: Spring FrameworkAs an alternative to JpaTransactionManager, Spring also allows for multi-resource transaction coordination through JTA, either in a Jakarta EE environment or
kwaksh2319.tistory.com
Hibernate
Hibernate has 55 repositories available. Follow their code on GitHub.
github.com
먼저 hibernate 란 뭘까?
Hibernate는 자바 기반의 객체 관계 매핑(ORM) 라이브러리입니다. ORM은 객체 지향 프로그래밍 언어를 사용하여 데이터베이스와의 상호작용을 추상화하는 프로그래밍 기법 중 하나로, 개발자가 객체 지향적인 방식으로 데이터베이스를 다룰 수 있게 해줍니다. Hibernate는 이 ORM을 구현한 대표적인 프레임워크 중 하나입니다.
Hibernate의 주요 목적은 Java 어플리케이션에서 사용하는 데이터베이스 작업을 단순화하고, 데이터베이스와 객체 사이의 불일치를 해결하는 것입니다. 이를 통해 개발자는 복잡한 JDBC(Java Database Connectivity) 코드를 작성하는 대신, 더 간결하고 객체 지향적인 코드로 데이터베이스 작업을 수행할 수 있습니다.
-chat gpt-
chat gpt 선생님께서는 이렇게 설명하신다.역시나 정확하시다.
그렇다면 hibrernate의 github을 통해서 코드와 현재 사용중인 내 코드와 비교하면서 확인해보자.
https://github.com/hibernate/hibernate-orm
GitHub - hibernate/hibernate-orm: Hibernate's core Object/Relational Mapping functionality
Hibernate's core Object/Relational Mapping functionality - hibernate/hibernate-orm
github.com

일반적으로 gradle을 이용하면 hibernate를 사용할수 있습니다.
실제 라이브러리는

이런식으로 있습니다. hibernate 대략적인 구조를 보죠.


유저 가이드를 통해서 보면 이런식으로 되어있습니다.
그렇다면 대략적인 형태를 보았으니 코드로 확인해보자!
spring jpa 의 순수 jpa에서 확인할수 잇지만 필자는 spring jpa로 개발을 많이 하기때문에 spring jpa 로 확인해보겠습니다.
jpaRepostory의 구현체이다.
SimpleJpaRepository 클래스 실제로 실행된다면 아래의 클래스가 jpaRepostory가 실행됩니다.
여기서 라이브러리를 보면
package org.springframework.data.jpa.repository.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.QueryHints.NoHints;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.ProxyUtils;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;
@Nullable
private CrudMethodMetadata metadata;
private EscapeCharacter escapeCharacter;
public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
this.escapeCharacter = EscapeCharacter.DEFAULT;
Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
Assert.notNull(entityManager, "EntityManager must not be null!");
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
}
public SimpleJpaRepository(Class<T> domainClass, EntityManager em) {
this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
}
public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.metadata = crudMethodMetadata;
}
public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
this.escapeCharacter = escapeCharacter;
}
@Nullable
protected CrudMethodMetadata getRepositoryMethodMetadata() {
return this.metadata;
}
protected Class<T> getDomainClass() {
return this.entityInformation.getJavaType();
}
private String getDeleteAllQueryString() {
return QueryUtils.getQueryString("delete from %s x", this.entityInformation.getEntityName());
}
private String getCountQueryString() {
String countQuery = String.format("select count(%s) from %s x", this.provider.getCountQueryPlaceholder(), "%s");
return QueryUtils.getQueryString(countQuery, this.entityInformation.getEntityName());
}
@Transactional
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
@Transactional
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (!this.entityInformation.isNew(entity)) {
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = this.em.find(type, this.entityInformation.getId(entity));
if (existing != null) {
this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
}
}
}
@Transactional
public void deleteAllById(Iterable<? extends ID> ids) {
Assert.notNull(ids, "Ids must not be null!");
Iterator var2 = ids.iterator();
while(var2.hasNext()) {
ID id = var2.next();
this.deleteById(id);
}
}
@Transactional
public void deleteAllByIdInBatch(Iterable<ID> ids) {
Assert.notNull(ids, "Ids must not be null!");
if (ids.iterator().hasNext()) {
if (this.entityInformation.hasCompositeId()) {
List<T> entities = new ArrayList();
ids.forEach((id) -> {
entities.add(this.getReferenceById(id));
});
this.deleteAllInBatch(entities);
} else {
String queryString = String.format("delete from %s x where %s in :ids", this.entityInformation.getEntityName(), this.entityInformation.getIdAttribute().getName());
Query query = this.em.createQuery(queryString);
if (Collection.class.isInstance(ids)) {
query.setParameter("ids", ids);
} else {
Collection<ID> idsCollection = (Collection)StreamSupport.stream(ids.spliterator(), false).collect(Collectors.toCollection(ArrayList::new));
query.setParameter("ids", idsCollection);
}
query.executeUpdate();
}
}
}
@Transactional
public void deleteAll(Iterable<? extends T> entities) {
Assert.notNull(entities, "Entities must not be null!");
Iterator var2 = entities.iterator();
while(var2.hasNext()) {
T entity = var2.next();
this.delete(entity);
}
}
@Transactional
public void deleteAllInBatch(Iterable<T> entities) {
Assert.notNull(entities, "Entities must not be null!");
if (entities.iterator().hasNext()) {
QueryUtils.applyAndBind(QueryUtils.getQueryString("delete from %s x", this.entityInformation.getEntityName()), entities, this.em).executeUpdate();
}
}
@Transactional
public void deleteAll() {
Iterator var1 = this.findAll().iterator();
while(var1.hasNext()) {
T element = var1.next();
this.delete(element);
}
}
@Transactional
public void deleteAllInBatch() {
this.em.createQuery(this.getDeleteAllQueryString()).executeUpdate();
}
public Optional<T> findById(ID id) {
Assert.notNull(id, "The given id must not be null!");
Class<T> domainType = this.getDomainClass();
if (this.metadata == null) {
return Optional.ofNullable(this.em.find(domainType, id));
} else {
LockModeType type = this.metadata.getLockModeType();
Map<String, Object> hints = new HashMap();
this.getQueryHints().withFetchGraphs(this.em).forEach(hints::put);
return Optional.ofNullable(type == null ? this.em.find(domainType, id, hints) : this.em.find(domainType, id, type, hints));
}
}
protected QueryHints getQueryHints() {
return (QueryHints)(this.metadata == null ? NoHints.INSTANCE : DefaultQueryHints.of(this.entityInformation, this.metadata));
}
/** @deprecated */
@Deprecated
public T getOne(ID id) {
return this.getReferenceById(id);
}
/** @deprecated */
@Deprecated
public T getById(ID id) {
return this.getReferenceById(id);
}
public T getReferenceById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return this.em.getReference(this.getDomainClass(), id);
}
public boolean existsById(ID id) {
Assert.notNull(id, "The given id must not be null!");
if (this.entityInformation.getIdAttribute() == null) {
return this.findById(id).isPresent();
} else {
String placeholder = this.provider.getCountQueryPlaceholder();
String entityName = this.entityInformation.getEntityName();
Iterable<String> idAttributeNames = this.entityInformation.getIdAttributeNames();
String existsQuery = QueryUtils.getExistsQueryString(entityName, placeholder, idAttributeNames);
TypedQuery<Long> query = this.em.createQuery(existsQuery, Long.class);
if (!this.entityInformation.hasCompositeId()) {
query.setParameter((String)idAttributeNames.iterator().next(), id);
return (Long)query.getSingleResult() == 1L;
} else {
Iterator var7 = idAttributeNames.iterator();
while(var7.hasNext()) {
String idAttributeName = (String)var7.next();
Object idAttributeValue = this.entityInformation.getCompositeIdAttributeValue(id, idAttributeName);
boolean complexIdParameterValueDiscovered = idAttributeValue != null && !query.getParameter(idAttributeName).getParameterType().isAssignableFrom(idAttributeValue.getClass());
if (complexIdParameterValueDiscovered) {
return this.findById(id).isPresent();
}
query.setParameter(idAttributeName, idAttributeValue);
}
return (Long)query.getSingleResult() == 1L;
}
}
}
public List<T> findAll() {
return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
}
public List<T> findAllById(Iterable<ID> ids) {
Assert.notNull(ids, "Ids must not be null!");
if (!ids.iterator().hasNext()) {
return Collections.emptyList();
} else if (!this.entityInformation.hasCompositeId()) {
Collection<ID> idCollection = Streamable.of(ids).toList();
ByIdsSpecification<T> specification = new ByIdsSpecification(this.entityInformation);
TypedQuery<T> query = this.getQuery(specification, (Sort)Sort.unsorted());
return query.setParameter(specification.parameter, idCollection).getResultList();
} else {
List<T> results = new ArrayList();
Iterator var3 = ids.iterator();
while(var3.hasNext()) {
ID id = var3.next();
this.findById(id).ifPresent(results::add);
}
return results;
}
}
public List<T> findAll(Sort sort) {
return this.getQuery((Specification)null, (Sort)sort).getResultList();
}
public Page<T> findAll(Pageable pageable) {
return (Page)(isUnpaged(pageable) ? new PageImpl(this.findAll()) : this.findAll((Specification)null, pageable));
}
public Optional<T> findOne(@Nullable Specification<T> spec) {
try {
return Optional.of(this.getQuery(spec, Sort.unsorted()).getSingleResult());
} catch (NoResultException var3) {
return Optional.empty();
}
}
public List<T> findAll(@Nullable Specification<T> spec) {
return this.getQuery(spec, Sort.unsorted()).getResultList();
}
public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = this.getQuery(spec, pageable);
return (Page)(isUnpaged(pageable) ? new PageImpl(query.getResultList()) : this.readPage(query, this.getDomainClass(), pageable, spec));
}
public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
return this.getQuery(spec, sort).getResultList();
}
public <S extends T> Optional<S> findOne(Example<S> example) {
try {
return Optional.of(this.getQuery(new ExampleSpecification(example, this.escapeCharacter), example.getProbeType(), (Sort)Sort.unsorted()).getSingleResult());
} catch (NoResultException var3) {
return Optional.empty();
}
}
public <S extends T> long count(Example<S> example) {
return executeCountQuery(this.getCountQuery(new ExampleSpecification(example, this.escapeCharacter), example.getProbeType()));
}
public <S extends T> boolean exists(Example<S> example) {
Specification<S> spec = new ExampleSpecification(example, this.escapeCharacter);
CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
cq.select(this.em.getCriteriaBuilder().literal(1));
this.applySpecificationToCriteria(spec, example.getProbeType(), cq);
TypedQuery<Integer> query = this.applyRepositoryMethodMetadata(this.em.createQuery(cq));
return query.setMaxResults(1).getResultList().size() == 1;
}
public boolean exists(Specification<T> spec) {
CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
cq.select(this.em.getCriteriaBuilder().literal(1));
this.applySpecificationToCriteria(spec, this.getDomainClass(), cq);
TypedQuery<Integer> query = this.applyRepositoryMethodMetadata(this.em.createQuery(cq));
return query.setMaxResults(1).getResultList().size() == 1;
}
public <S extends T> List<S> findAll(Example<S> example) {
return this.getQuery(new ExampleSpecification(example, this.escapeCharacter), example.getProbeType(), (Sort)Sort.unsorted()).getResultList();
}
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
return this.getQuery(new ExampleSpecification(example, this.escapeCharacter), example.getProbeType(), (Sort)sort).getResultList();
}
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
ExampleSpecification<S> spec = new ExampleSpecification(example, this.escapeCharacter);
Class<S> probeType = example.getProbeType();
TypedQuery<S> query = this.getQuery(new ExampleSpecification(example, this.escapeCharacter), probeType, (Pageable)pageable);
return (Page)(isUnpaged(pageable) ? new PageImpl(query.getResultList()) : this.readPage(query, probeType, pageable, spec));
}
public <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
Assert.notNull(example, "Sample must not be null!");
Assert.notNull(queryFunction, "Query function must not be null!");
Function<Sort, TypedQuery<S>> finder = (sort) -> {
ExampleSpecification<S> spec = new ExampleSpecification(example, this.escapeCharacter);
Class<S> probeType = example.getProbeType();
return this.getQuery(spec, probeType, (Sort)sort);
};
FluentQuery.FetchableFluentQuery<S> fluentQuery = new FetchableFluentQueryByExample(example, finder, this::count, this::exists, this.em, this.escapeCharacter);
return queryFunction.apply(fluentQuery);
}
public long count() {
return (Long)this.em.createQuery(this.getCountQueryString(), Long.class).getSingleResult();
}
public long count(@Nullable Specification<T> spec) {
return executeCountQuery(this.getCountQuery(spec, this.getDomainClass()));
}
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
@Transactional
public <S extends T> S saveAndFlush(S entity) {
S result = this.save(entity);
this.flush();
return result;
}
@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList();
Iterator var3 = entities.iterator();
while(var3.hasNext()) {
S entity = var3.next();
result.add(this.save(entity));
}
return result;
}
@Transactional
public <S extends T> List<S> saveAllAndFlush(Iterable<S> entities) {
List<S> result = this.saveAll(entities);
this.flush();
return result;
}
@Transactional
public void flush() {
this.em.flush();
}
/** @deprecated */
@Deprecated
protected Page<T> readPage(TypedQuery<T> query, Pageable pageable, @Nullable Specification<T> spec) {
return this.readPage(query, this.getDomainClass(), pageable, spec);
}
protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable, @Nullable Specification<S> spec) {
if (pageable.isPaged()) {
query.setFirstResult((int)pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
}
return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> {
return executeCountQuery(this.getCountQuery(spec, domainClass));
});
}
protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Pageable pageable) {
Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
return this.getQuery(spec, this.getDomainClass(), sort);
}
protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Pageable pageable) {
Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
return this.getQuery(spec, domainClass, sort);
}
protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Sort sort) {
return this.getQuery(spec, this.getDomainClass(), sort);
}
protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<S> query = builder.createQuery(domainClass);
Root<S> root = this.applySpecificationToCriteria(spec, domainClass, query);
query.select(root);
if (sort.isSorted()) {
query.orderBy(QueryUtils.toOrders(sort, root, builder));
}
return this.applyRepositoryMethodMetadata(this.em.createQuery(query));
}
/** @deprecated */
@Deprecated
protected TypedQuery<Long> getCountQuery(@Nullable Specification<T> spec) {
return this.getCountQuery(spec, this.getDomainClass());
}
protected <S extends T> TypedQuery<Long> getCountQuery(@Nullable Specification<S> spec, Class<S> domainClass) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = this.applySpecificationToCriteria(spec, domainClass, query);
if (query.isDistinct()) {
query.select(builder.countDistinct(root));
} else {
query.select(builder.count(root));
}
query.orderBy(Collections.emptyList());
return this.em.createQuery(query);
}
private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass, CriteriaQuery<S> query) {
Assert.notNull(domainClass, "Domain class must not be null!");
Assert.notNull(query, "CriteriaQuery must not be null!");
Root<U> root = query.from(domainClass);
if (spec == null) {
return root;
} else {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
return root;
}
}
private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {
if (this.metadata == null) {
return query;
} else {
LockModeType type = this.metadata.getLockModeType();
TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);
this.applyQueryHints(toReturn);
return toReturn;
}
}
private void applyQueryHints(Query query) {
this.getQueryHints().withFetchGraphs(this.em).forEach(query::setHint);
}
private static long executeCountQuery(TypedQuery<Long> query) {
Assert.notNull(query, "TypedQuery must not be null!");
List<Long> totals = query.getResultList();
long total = 0L;
Long element;
for(Iterator var4 = totals.iterator(); var4.hasNext(); total += element == null ? 0L : element) {
element = (Long)var4.next();
}
return total;
}
private static boolean isUnpaged(Pageable pageable) {
return pageable.isUnpaged();
}
private static class ExampleSpecification<T> implements Specification<T> {
private static final long serialVersionUID = 1L;
private final Example<T> example;
private final EscapeCharacter escapeCharacter;
ExampleSpecification(Example<T> example, EscapeCharacter escapeCharacter) {
Assert.notNull(example, "Example must not be null!");
Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!");
this.example = example;
this.escapeCharacter = escapeCharacter;
}
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return QueryByExamplePredicateBuilder.getPredicate(root, cb, this.example, this.escapeCharacter);
}
}
private static final class ByIdsSpecification<T> implements Specification<T> {
private static final long serialVersionUID = 1L;
private final JpaEntityInformation<T, ?> entityInformation;
@Nullable
ParameterExpression<Collection<?>> parameter;
ByIdsSpecification(JpaEntityInformation<T, ?> entityInformation) {
this.entityInformation = entityInformation;
}
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<?> path = root.get(this.entityInformation.getIdAttribute());
this.parameter = cb.parameter(Collection.class);
return path.in(this.parameter);
}
}
}
hibernate에서 사용하는 라이브러리이다.

가장 중요한게 EntitiManger를 먼저 보자.
private final EntityManager em;
코드에

hibernate에서 사용하는걸 코드에서 볼수 있습니다.
그렇다면


Session 클래스를 보자.
보면 EntityMager를 상속받는 Hiberante 라이브러리에 있는 클래스이다.

그렇다면 다음 구조를 확인해보자.
SessionImpl 클래스이다.

즉 이런식의 구조로 되어있네요.
그렇다면 간단하게 jpa Repostory의 findAll을 보자
prodocts->finaAll();
SimpleJpaReopository .findAll()
public List<T> findAll() {
return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
}
finAll()->GetQuery(spec,sort)
protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Sort sort) {
return this.getQuery(spec, this.getDomainClass(), sort);
}
finAll()-> GetQuery( spec,sort )->getQuery( spec,domainClass,sort );
protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<S> query = builder.createQuery(domainClass);
Root<S> root = this.applySpecificationToCriteria(spec, domainClass, query);
query.select(root);
if (sort.isSorted()) {
query.orderBy(QueryUtils.toOrders(sort, root, builder));
}
return this.applyRepositoryMethodMetadata(this.em.createQuery(query));
}
보시면 EntityManger가 가지고 있는 Criteria api 를 이용해서 쿼리문을 작성해서
query.select(root);
if (sort.isSorted()) {
query.orderBy(QueryUtils.toOrders(sort, root, builder));
}
데이터를 불러옵니다.
applySpecificationToCriteria // check 용
applyRepositoryMethodMetadata // check 용
이런식으로 대략적으로 흘러갑니다.
다음에는 데이터를 조회할때나 저장할때 어떻게 흘러가는지 디버깅 모드로 한번 확인해보겠습니다.
'프로그래밍언어 > JAVA' 카테고리의 다른 글
Virtual Thread(가상 스레드) (0) | 2024.09.01 |
---|---|
hashcode와 equals (0) | 2024.08.05 |
순수 JAVA JPA (0) | 2024.03.17 |
getDeclaredConstructor().newInstance()? (0) | 2024.03.16 |
Reflection API란? (0) | 2024.03.16 |