I am trying to build a small app with Reactive jackson hibernate panache mysql as DB.
I am getting the below error.
"stackTrace": "java.lang.IllegalStateException: No pool has been
defined for persistence unit default-reactive\n\tat
io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.registerVertxAndPool(FastBootHibernateReactivePersistenceProvider.java:233)\n\tat
io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.rewireMetadataAndExtractServiceRegistry(FastBootHibernateReactivePersistenceProvider.java:180)\n\tat
io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.getEntityManagerFactoryBuilderOrNull(FastBootHibernateReactivePersistenceProvider.java:156)\n\tat
io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.createEntityManagerFactory(FastBootHibernateReactivePersistenceProvider.java:82)\n\tat
javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)\n\tat
javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)\n\tat
io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:118)\n\tat
io.quarkus.hibernate.orm.runtime.JPAConfig.startAll(JPAConfig.java:42)\n\tat
io.quarkus.hibernate.orm.runtime.JPAConfig_Subclass.startAll$$superaccessor5(JPAConfig_Subclass.zig:769)\n\tat
io.quarkus.hibernate.orm.runtime.JPAConfig_Subclass$$function$$5.apply(JPAConfig_Subclass$$function$$5.zig:29)\n\tat
io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)\n\tat
io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)\n\tat
io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)\n\tat
io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)\n\tat
io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)\n\tat
io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)\n\tat
io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)\n\tat
io.quarkus.hibernate.orm.runtime.JPAConfig_Subclass.startAll(JPAConfig_Subclass.zig:727)\n\tat
io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder.startAllPersistenceUnits(HibernateOrmRecorder.java:88)\n\tat
io.quarkus.deployment.steps.HibernateOrmProcessor$startPersistenceUnits951856026.deploy_0(HibernateOrmProcessor$startPersistenceUnits951856026.zig:74)\n\tat
io.quarkus.deployment.steps.HibernateOrmProcessor$startPersistenceUnits951856026.deploy(HibernateOrmProcessor$startPersistenceUnits951856026.zig:40)\n\tat
io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:751)\n\tat
io.quarkus.runtime.Application.start(Application.java:90)\n\tat
io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:100)\n\tat
io.quarkus.runtime.Quarkus.run(Quarkus.java:66)\n\tat
io.quarkus.runtime.Quarkus.run(Quarkus.java:42)\n\tat
io.quarkus.runtime.Quarkus.run(Quarkus.java:119)\n\tat
io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:29)\n\tat
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method)\n\tat
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)\n\tat
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat
java.base/java.lang.reflect.Method.invoke(Method.java:567)\n\tat
io.quarkus.runner.bootstrap.StartupActionImpl$3.run(StartupActionImpl.java:134)\n\tat
java.base/java.lang.Thread.run(Thread.java:831)\n"
Any idea What am missing?.
I have models
#Entity
public class Nation extends PanacheEntity {
#Column
public String country;
public Nation(String country, List<State> states) {
this.country = country;
this.states = states;
}
#OneToMany(cascade = {CascadeType.ALL})
public List<State> states = new ArrayList<>();
public Nation() {
}
}
#Entity
public class State extends PanacheEntity {
public State(String state, List<District> districts) {
this.state = state;
this.districts = districts;
}
#Column
public String state;
#OneToMany
public List<District> districts = new ArrayList<>();
public State() {
}
}
#Entity
public class District extends PanacheEntity {
public District(String district, List<Village> villages) {
this.district = district;
this.villages = villages;
}
#Column
public String district;
#OneToMany
public List<Village> villages = new ArrayList<>();
public District() {
}
}
#Entity
public class Village extends PanacheEntity {
#Column
public String village;
public Village(String village) {
this.village = village;
}
public Village() {
}
}
#Path("/nation")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#ApplicationScoped
public class NationResource {
#Inject
NationRepository nationRepository;
/* #Inject
public NationResource(NationRepository nationRepository) {
this.nationRepository = nationRepository;
}*/
#POST
#Path("save")
public Uni<Void> saveNation(Nation nation) {
return nationRepository.persist(nation);
}
#GET
public Uni<List<Nation>> getNations() {
return nationRepository.listAll();
}
#GET
#Path("{id}")
public Uni<Nation> getNation(#PathParam("id") Long id) {
return nationRepository.findById(id);
}
}
quarkus:
http:
port: 4754
log:
console:
json:
pretty-print: true
date-format: "YYYY-MM-dd HH:mm:ss"
exception-output-type: "detailed-and-formatted"
# configure your datasource
datasource:
db-kind: mysql
username: root
password: root
reactive:
url: vertx-reactive:mysql://localhost:3306/garrsolutions
# drop and create the database at startup (use `update` to only update the schema)
hibernate-orm:
database:
generation: drop-and-create
I resolved this problem by adding below snip into pom.xml dependencies:
<!-- JDBC driver dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
In my case, i was trying to use H2 Db and i got the same problem. I resolved this problem using a map based approach like the exemple:
From:
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:guitars
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.packages=package br.com.mp.product.models
To:
quarkus.datasource."guitars".db-kind=h2
quarkus.datasource."guitars".jdbc.url=jdbc:h2:mem:guitars
quarkus.hibernate-orm."guitars".database.generation=drop-and-create
quarkus.hibernate-orm."guitars".packages=package br.com.mp.product.models
In this way the hibernate can find the specified class from map, but i didn't try with MySql Db like is your case.
I saw this example in this link: https://quarkus.io/guides/hibernate-orm
Related
I am trying to create a Spring Boot CRUD application using Cassandra. I created a docker image and I already configured Cassandra, in CassandraConfiguration class but still is not creating my tables.
My CassandraConfiguration.java code:
#Configuration
#EnableCassandraRepositories
public class CassandraConfiguration extends AbstractCassandraConfiguration {
#Value("${env.values.cassandra.keyspace.name}")
private String keyspaceName;
.......................................
#Override
protected String getKeyspaceName() {
return keyspaceName;
}
#Override
protected int getPort() {
return contactPort;
}
#Override
protected String getContactPoints() {
return contactPoint;
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
#Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
return List.of(
CreateKeyspaceSpecification.createKeyspace(keyspaceName)
.ifNotExists()
.withSimpleReplication(3));
}
#Bean
#Override
public CqlSessionFactoryBean cassandraSession() {
CqlSessionFactoryBean cassandraSession =
super.cassandraSession(); // super session should be called only once
cassandraSession.setUsername(username);
cassandraSession.setPassword(password);
return cassandraSession;
}
}
My entity:
#Table
#AllArgsConstructor
#NoArgsConstructor
#Builder
#EqualsAndHashCode(of = {"id"})
#Getter
#Setter
public class Account {
#PrimaryKey private String id = UUID.randomUUID().toString();
private String username;
private String email;
private String name;
private String password;
}
My pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
After I've done quick check of the code and configuration you posted, nothing was obvious to me as being incorrect.
My suggestion is to review the application logs looking specifically for errors and warnings from the Cassandra Java driver. Chances are the Cassandra cluster is unreachable from your application usually because of some networking issue.
You will need to verify that there is network connectivity between your application and the contact points + CQL port you've configured. Cheers!
I have connected spring boot to an MySQL database running on a VPS. I've mapped the entity class, repository, service and controller. But when calling the get method for "findAll" I get an empty list in postman. When I query the table in MySQL workbench I do get data, so its not empty.
Did I miss something?
My entity class:
#Entity
public class Requests {
#Id
#Column
private Long id;
#Column
private Long url_id;
#Column
private Date timestamp;
#Column
private String method;
#Column
private String document;
#Column
private String mime_type;
#Column
private char is_html;
#Column
private int status_code;
#Column
private String reason;
#Column
private String cookies;
#Column
private String request;
#Column
private String response;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUrl_id() {
return url_id;
}
public void setUrl_id(Long url_id) {
this.url_id = url_id;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getDocument() {
return document;
}
public void setDocument(String document) {
this.document = document;
}
public String getMime_type() {
return mime_type;
}
public void setMime_type(String mime_type) {
this.mime_type = mime_type;
}
public char getIs_html() {
return is_html;
}
public void setIs_html(char is_html) {
this.is_html = is_html;
}
public int getStatus_code() {
return status_code;
}
public void setStatus_code(int status_code) {
this.status_code = status_code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getCookies() {
return cookies;
}
public void setCookies(String cookies) {
this.cookies = cookies;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
}
My reposiroty:
public interface RequestsRepository extends JpaRepository<Requests, Long> {
}
My service:
#Service
public class RequestsService {
private final RequestsRepository requestsRepository;
public RequestsService(RequestsRepository requestsRepository) {
this.requestsRepository = requestsRepository;
}
public List<RequestsDTO> findAll() {
return requestsRepository.findAll()
.stream()
.map(requests -> mapToDTO(requests, new RequestsDTO()))
.collect(Collectors.toList());
}
private RequestsDTO mapToDTO(final Requests requests, final RequestsDTO requestsDTO) {
requestsDTO.setId(requests.getId());
requestsDTO.setUrl_id(requests.getUrl_id());
requestsDTO.setTimestamp(requests.getTimestamp());
requestsDTO.setMethod(requests.getMethod());
requestsDTO.setDocument(requests.getDocument());
requestsDTO.setMime_type(requests.getMime_type());
requestsDTO.setIs_html(requests.getIs_html());
requestsDTO.setStatus_code(requests.getStatus_code());
requestsDTO.setReason(requests.getReason());
requestsDTO.setCookies(requests.getCookies());
requestsDTO.setRequest(requests.getRequest());
requestsDTO.setResponse(requests.getResponse());
return requestsDTO;
}
private Requests mapToEntity(final RequestsDTO requestsDTO, final Requests requests) {
requests.setId(requestsDTO.getId());
requests.setUrl_id(requestsDTO.getUrl_id());
requests.setTimestamp(requestsDTO.getTimestamp());
requests.setMethod(requestsDTO.getMethod());
requests.setDocument(requestsDTO.getDocument());
requests.setMime_type(requestsDTO.getMime_type());
requests.setIs_html(requestsDTO.getIs_html());
requests.setStatus_code(requestsDTO.getStatus_code());
requests.setReason(requestsDTO.getReason());
requests.setCookies(requestsDTO.getCookies());
requests.setRequest(requestsDTO.getRequest());
requests.setResponse(requestsDTO.getResponse());
return requests;
}
}
And my controller:
#RestController
#RequestMapping(value = "/api/crawler", produces = MediaType.APPLICATION_JSON_VALUE)
public class RequestsController {
private final RequestsService requestsService;
public RequestsController(RequestsService requestsService) {
this.requestsService = requestsService;
}
#GetMapping
public ResponseEntity<List<RequestsDTO>> getAllRequests() {return ResponseEntity.ok(requestsService.findAll()); }
}
EDIT
My crawler domain config
/**
* Data source for the MySQL crawler database
*/
#Configuration
#EntityScan(basePackages = "cs.crawler.server.projectcs.domain.crawlerdb")
#EnableJpaRepositories(basePackages = "cs.crawler.server.projectcs.repos.crawlerdb")
#EnableTransactionManagement
public class CrawlerDomainConfig {
#Bean
#ConfigurationProperties("spring.datasource.crawler")
public DataSourceProperties crawlerDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.datasource.crawler.configuration")
public HikariDataSource secondDataSource(DataSourceProperties secondDataSourceProperties) {
return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}
My config YAML:
spring:
datasource:
users:
url: ${JDBC_DATABASE_URL:jdbc:h2:mem:projectcs}
username: ${JDBC_DATABASE_USERNAME:sa}
password: ${JDBC_DATABASE_PASSWORD:}
crawler:
url: ${JDBC_DATABASE_URL:jdbc:mysql://VPSIPHERE:3306/darcweb?createDatabaseIfNotExist=false}
username: ${JDBC_DATABASE_USERNAME:root}
password: ${JDBC_DATABASE_PASSWORD:mypassword}
dbcp2:
max-wait-millis: 30000
validation-query: "SELECT 1"
validation-query-timeout: 30
jpa:
hibernate:
ddl-auto: update
open-in-view: false
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
id:
new_generator_mappings: true
springdoc:
pathsToMatch: /api/**
You have 2 datasoruce dependencies (mysql and h2). You need to specify which datasource for spring to use - it picks h2 by default which is the reason you don't find anything.
Add following lines to your application.properties file to specify the database:
spring.datasource.url=jdbc:mysql://localhost:3306/DATABASE_NAME
spring.datasource.username=USERNAME
spring.datasource.password=PASSWORD
To use h2 for tests for example you can overwrite application.properties for h2 under your test folder in resources
In case you want to use both databases simultaneously in your application you would need to define both and set them in runtime into your enviroment like shown here:
https://www.baeldung.com/spring-data-jpa-multiple-databases
I am new to spring.
I just tried successfully using an entity class without #Id in Spring Data JDBC
Custom query was added in my repository for retrieving data from 2 mysql tables and returning an entity having the joined table data.
If I plan to use only custom queries, am I missing anything here?
Here's my entity class without #Id or #Entity:
public class Item
{
private long id;
private String code;
private String itemName;
private String groupName;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
Repository layer:
#Repository
public interface ItemRepository extends PagingAndSortingRepository<Item, Long>
{
#Query("SELECT a.id, a.code, a.name AS item_name,
b.name as group_name from item a, item_group b
WHERE a.group_id = b.id AND a.id=:id")
Item findItemById(#Param("id") Long id);
}
Service layer:
#Service
public class ItemServiceImpl implements ItemService
{
private final ItemRepository itemRepository;
public ItemServiceImpl(ItemRepository itemRepository)
{
this.itemRepository = itemRepository;
}
#Override
#Transactional(readOnly=true)
public Item findItemById(Long id)
{
return itemRepository.findItemById(id);
}
}
My updated main Configuration class in response to answer of Jens:
#SpringBootApplication
#EnableJdbcRepositories
public class SpringDataJdbcApplication extends AbstractJdbcConfiguration
{
public static void main(String[] args)
{
SpringApplication.run(SpringDataJdbcApplication.class, args);
}
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
return dataSourceBuilder.build();
}
#Bean
NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource)
{
return new NamedParameterJdbcTemplate(dataSource);
}
#Bean
PlatformTransactionManager transactionManager()
{
return new DataSourceTransactionManager(dataSource());
}
}
If you don't get any exceptions you should be fine. There shouldn't be anything in Spring Data JDBC that silently breaks when the id is not specified.
The problem is though: I don't consider it a feature that this works, but just accidental behaviour. This means it might break with any version, although replacing these methods with custom implementations based on a NamedParameterJdbcTemplate shouldn't be to hard, so the risk is limited.
The question though is: Why don't you add the #Id annotation, after all your entity does have an id. And the whole idea of a repository conceptually requires an id.
If it's working and you really don't want to use the annotations, you can do it. But I think that it's unnecessary complication. You can expect errors that would not be there if you had used the annotations and code will be harder to debug. If you are new in Spring I recommend to use annotations. But after all it depend on you how will you design your applications. For sure advantage of approach without annotations is higher control about database.
I've been following a lot of tutorial on how to get a list of result by referencing a specific column in the table.
I have this table.
I want to get the list of result with a plan_code "TEST123"
This is my code:
PlanRepository.java
public interface PlanCoverageRepository extends CrudRepository<PlanCoverage, Long> {
List<PlanCoverage> findAllByPlan_code(String plan_code);
}
PlanCoverageService.java
public interface PlanCoverageService {
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code);
}
PlanCoverageServiceImpl.java
#Service
#Transactional
public class PlanCoverageServiceImpl implements PlanCoverageService {
#Override
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code) {
return (List<PlanCoverage>) planCoverageRepository.findAllByPlan_code(plan_code);
}
}
PlanCoverageController.java
#Controller
#RequestMapping(value="/admin")
public class PlanCoverageController {
#Autowired
PlanCoverageService planCoverageService;
#RequestMapping(value="/Test/{plan_code}", method=RequestMethod.GET)
public ModelAndView test(#PathVariable String plan_code) {
ModelAndView model = new ModelAndView();
PlanCoverage planCoverage = (PlanCoverage) planCoverageService.getAllPlanCoverageByPlanCode(plan_code);
model.addObject("planCoverageForm",planCoverage);
model.setViewName("plan_coverage_form");
return model;
}
}
PlanCoverage.java
#Entity
#Table(name="plan_coverage")
public class PlanCoverage {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long coverage_id;
#Column(name="plan_code")
private String plan_code;
#Column(name="coverage_description")
private String coverage_description;
/..getters and setters
#ManyToOne()
#JoinColumn(name="plan_code", referencedColumnName = "plan_code",insertable=false, updatable=false)
private Plan plan;
public Plan getPlan() {
return plan;
}
public void setPlan(Plan plan) {
this.plan = plan;
}
}
Please help me. I've been stuck with these for a few days and non of the tutorials seems to work on me. Thank you so much!!
You have messed up with the convention that spring boot is using to compose query methods. The case of the fields in the entity should follow the lower camel-case scheme, like so:
#Column(name="plan_code")
private String planCode;
and then the query method in PlanCoverageRepository should be:
List<PlanCoverage> findAllByPlanCode(String planCode);
I have a SDR project and I am successfully validating the user entity for POST request but as soon as I update an existing entity using either PATCH or PUT the DB is updated BEFORE the validation is executed (the validator is being executed and error is returned but the DB is being updated anyway).
Do I need to setup a separate config for update ? Am I missing an extra step for that?
Entity
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_gen")
#SequenceGenerator(name = "member_id_gen", sequenceName = "member_id_seq")
#Id
#JsonIgnore
private long id;
#Version
private Integer version;
#NotNull
protected String firstName;
#NotNull
protected String lastName;
#Valid
protected String email;
}
Repository
#RepositoryRestResource(collectionResourceRel = "members", path = "member")
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
public Member findByFirstName(String firstName);
public Member findByLastName(String lastName);
}
Validator
#Component
public class BeforeSaveMemberValidator implements Validator {
public BeforeSaveMemberValidator() {}
private String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
#Override
public boolean supports(Class<?> clazz) {
return Member.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
if(ObjectUtils.isEmpty(member.getFirstName())) {
errors.rejectValue("firstName", "member.firstName.empty");
}
if(ObjectUtils.isEmpty(member.getLastName())) {
errors.rejectValue("lastName", "member.lastName.empty");
}
if(!ObjectUtils.isEmpty(member.getDni()) && !member.getDni().matches("^[a-zA-Z0-9]*$")) {
errors.rejectValue("dni", "member.dni.invalid");
}
if(!ObjectUtils.isEmpty(member.getEmail()) && !member.getEmail().matches(EMAIL_REGEX)) {
errors.rejectValue("email", "member.email.notValid");
}
}
}
BeforeSave service
#Service
#RepositoryEventHandler(Member.class)
public class MemberService {
#HandleBeforeCreate
#HandleBeforeSave
#Transactional
public void beforeCreate(Member member) {
...
}
}
I think you should rename your validator, for example, to MemberValidator then assign it as described here:
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new MemberValidator());
v.addValidator("beforeSave", new MemberValidator());
}
But I suggest you to use Bean validation instead of your custom validators. To use it in SDR project you can inject LocalValidatorFactoryBean, then assign it for 'beforeCreate' and 'beforeSave' events in configureValidatingRepositoryEventListener:
#Configuration
#RequiredArgsConstructor // Lombok annotation
public class RepoRestConfig extends RepositoryRestConfigurerAdapter {
#NonNull private final LocalValidatorFactoryBean validatorFactoryBean;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validatorFactoryBean);
v.addValidator("beforeSave", validatorFactoryBean);
super.configureValidatingRepositoryEventListener(v);
}
}
In this case your SDR will automatically validate payloads of POST, PUT and PATCH requests for all exposed SDR repositories.
See my example for more details.